mcp-ticketer 0.12.0__py3-none-any.whl → 2.2.13__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of mcp-ticketer might be problematic. Click here for more details.
- mcp_ticketer/__init__.py +10 -10
- mcp_ticketer/__version__.py +1 -1
- mcp_ticketer/_version_scm.py +1 -0
- mcp_ticketer/adapters/aitrackdown.py +507 -6
- mcp_ticketer/adapters/asana/adapter.py +229 -0
- mcp_ticketer/adapters/asana/mappers.py +14 -0
- mcp_ticketer/adapters/github/__init__.py +26 -0
- mcp_ticketer/adapters/github/adapter.py +3229 -0
- mcp_ticketer/adapters/github/client.py +335 -0
- mcp_ticketer/adapters/github/mappers.py +797 -0
- mcp_ticketer/adapters/github/queries.py +692 -0
- mcp_ticketer/adapters/github/types.py +460 -0
- mcp_ticketer/adapters/hybrid.py +47 -5
- mcp_ticketer/adapters/jira/__init__.py +35 -0
- mcp_ticketer/adapters/jira/adapter.py +1351 -0
- mcp_ticketer/adapters/jira/client.py +271 -0
- mcp_ticketer/adapters/jira/mappers.py +246 -0
- mcp_ticketer/adapters/jira/queries.py +216 -0
- mcp_ticketer/adapters/jira/types.py +304 -0
- mcp_ticketer/adapters/linear/adapter.py +2730 -139
- mcp_ticketer/adapters/linear/client.py +175 -3
- mcp_ticketer/adapters/linear/mappers.py +203 -8
- mcp_ticketer/adapters/linear/queries.py +280 -3
- mcp_ticketer/adapters/linear/types.py +120 -4
- mcp_ticketer/analysis/__init__.py +56 -0
- mcp_ticketer/analysis/dependency_graph.py +255 -0
- mcp_ticketer/analysis/health_assessment.py +304 -0
- mcp_ticketer/analysis/orphaned.py +218 -0
- mcp_ticketer/analysis/project_status.py +594 -0
- mcp_ticketer/analysis/similarity.py +224 -0
- mcp_ticketer/analysis/staleness.py +266 -0
- mcp_ticketer/automation/__init__.py +11 -0
- mcp_ticketer/automation/project_updates.py +378 -0
- mcp_ticketer/cli/adapter_diagnostics.py +3 -1
- mcp_ticketer/cli/auggie_configure.py +17 -5
- mcp_ticketer/cli/codex_configure.py +97 -61
- mcp_ticketer/cli/configure.py +1288 -105
- mcp_ticketer/cli/cursor_configure.py +314 -0
- mcp_ticketer/cli/diagnostics.py +13 -12
- mcp_ticketer/cli/discover.py +5 -0
- mcp_ticketer/cli/gemini_configure.py +17 -5
- mcp_ticketer/cli/init_command.py +880 -0
- mcp_ticketer/cli/install_mcp_server.py +418 -0
- mcp_ticketer/cli/instruction_commands.py +6 -0
- mcp_ticketer/cli/main.py +267 -3175
- mcp_ticketer/cli/mcp_configure.py +821 -119
- mcp_ticketer/cli/mcp_server_commands.py +415 -0
- mcp_ticketer/cli/platform_detection.py +77 -12
- mcp_ticketer/cli/platform_installer.py +545 -0
- mcp_ticketer/cli/project_update_commands.py +350 -0
- mcp_ticketer/cli/setup_command.py +795 -0
- mcp_ticketer/cli/simple_health.py +12 -10
- mcp_ticketer/cli/ticket_commands.py +705 -103
- mcp_ticketer/cli/utils.py +113 -0
- mcp_ticketer/core/__init__.py +56 -6
- mcp_ticketer/core/adapter.py +533 -2
- mcp_ticketer/core/config.py +21 -21
- mcp_ticketer/core/exceptions.py +7 -1
- mcp_ticketer/core/label_manager.py +732 -0
- mcp_ticketer/core/mappers.py +31 -19
- mcp_ticketer/core/milestone_manager.py +252 -0
- mcp_ticketer/core/models.py +480 -0
- mcp_ticketer/core/onepassword_secrets.py +1 -1
- mcp_ticketer/core/priority_matcher.py +463 -0
- mcp_ticketer/core/project_config.py +132 -14
- mcp_ticketer/core/project_utils.py +281 -0
- mcp_ticketer/core/project_validator.py +376 -0
- mcp_ticketer/core/session_state.py +176 -0
- mcp_ticketer/core/state_matcher.py +625 -0
- mcp_ticketer/core/url_parser.py +425 -0
- mcp_ticketer/core/validators.py +69 -0
- mcp_ticketer/mcp/server/__main__.py +2 -1
- mcp_ticketer/mcp/server/diagnostic_helper.py +175 -0
- mcp_ticketer/mcp/server/main.py +106 -25
- mcp_ticketer/mcp/server/routing.py +723 -0
- mcp_ticketer/mcp/server/server_sdk.py +58 -0
- mcp_ticketer/mcp/server/tools/__init__.py +33 -11
- mcp_ticketer/mcp/server/tools/analysis_tools.py +854 -0
- mcp_ticketer/mcp/server/tools/attachment_tools.py +5 -5
- mcp_ticketer/mcp/server/tools/bulk_tools.py +259 -202
- mcp_ticketer/mcp/server/tools/comment_tools.py +74 -12
- mcp_ticketer/mcp/server/tools/config_tools.py +1391 -145
- mcp_ticketer/mcp/server/tools/diagnostic_tools.py +211 -0
- mcp_ticketer/mcp/server/tools/hierarchy_tools.py +870 -460
- mcp_ticketer/mcp/server/tools/instruction_tools.py +7 -5
- mcp_ticketer/mcp/server/tools/label_tools.py +942 -0
- mcp_ticketer/mcp/server/tools/milestone_tools.py +338 -0
- mcp_ticketer/mcp/server/tools/pr_tools.py +3 -7
- mcp_ticketer/mcp/server/tools/project_status_tools.py +158 -0
- mcp_ticketer/mcp/server/tools/project_update_tools.py +473 -0
- mcp_ticketer/mcp/server/tools/search_tools.py +209 -97
- mcp_ticketer/mcp/server/tools/session_tools.py +308 -0
- mcp_ticketer/mcp/server/tools/ticket_tools.py +1107 -124
- mcp_ticketer/mcp/server/tools/user_ticket_tools.py +218 -236
- mcp_ticketer/queue/queue.py +68 -0
- mcp_ticketer/queue/worker.py +1 -1
- mcp_ticketer/utils/__init__.py +5 -0
- mcp_ticketer/utils/token_utils.py +246 -0
- mcp_ticketer-2.2.13.dist-info/METADATA +1396 -0
- mcp_ticketer-2.2.13.dist-info/RECORD +158 -0
- mcp_ticketer-2.2.13.dist-info/top_level.txt +2 -0
- py_mcp_installer/examples/phase3_demo.py +178 -0
- py_mcp_installer/scripts/manage_version.py +54 -0
- py_mcp_installer/setup.py +6 -0
- py_mcp_installer/src/py_mcp_installer/__init__.py +153 -0
- py_mcp_installer/src/py_mcp_installer/command_builder.py +445 -0
- py_mcp_installer/src/py_mcp_installer/config_manager.py +541 -0
- py_mcp_installer/src/py_mcp_installer/exceptions.py +243 -0
- py_mcp_installer/src/py_mcp_installer/installation_strategy.py +617 -0
- py_mcp_installer/src/py_mcp_installer/installer.py +656 -0
- py_mcp_installer/src/py_mcp_installer/mcp_inspector.py +750 -0
- py_mcp_installer/src/py_mcp_installer/platform_detector.py +451 -0
- py_mcp_installer/src/py_mcp_installer/platforms/__init__.py +26 -0
- py_mcp_installer/src/py_mcp_installer/platforms/claude_code.py +225 -0
- py_mcp_installer/src/py_mcp_installer/platforms/codex.py +181 -0
- py_mcp_installer/src/py_mcp_installer/platforms/cursor.py +191 -0
- py_mcp_installer/src/py_mcp_installer/types.py +222 -0
- py_mcp_installer/src/py_mcp_installer/utils.py +463 -0
- py_mcp_installer/tests/__init__.py +0 -0
- py_mcp_installer/tests/platforms/__init__.py +0 -0
- py_mcp_installer/tests/test_platform_detector.py +17 -0
- mcp_ticketer/adapters/github.py +0 -1574
- mcp_ticketer/adapters/jira.py +0 -1258
- mcp_ticketer-0.12.0.dist-info/METADATA +0 -550
- mcp_ticketer-0.12.0.dist-info/RECORD +0 -91
- mcp_ticketer-0.12.0.dist-info/top_level.txt +0 -1
- {mcp_ticketer-0.12.0.dist-info → mcp_ticketer-2.2.13.dist-info}/WHEEL +0 -0
- {mcp_ticketer-0.12.0.dist-info → mcp_ticketer-2.2.13.dist-info}/entry_points.txt +0 -0
- {mcp_ticketer-0.12.0.dist-info → mcp_ticketer-2.2.13.dist-info}/licenses/LICENSE +0 -0
|
@@ -0,0 +1,880 @@
|
|
|
1
|
+
"""Init command for mcp-ticketer - adapter configuration and initialization.
|
|
2
|
+
|
|
3
|
+
This module handles the initialization of adapter configuration through the
|
|
4
|
+
'mcp-ticketer init' command. It provides:
|
|
5
|
+
- Auto-discovery of adapter configuration from .env files
|
|
6
|
+
- Interactive prompts for manual adapter configuration
|
|
7
|
+
- Configuration validation with retry loops
|
|
8
|
+
- Support for Linear, JIRA, GitHub, and AITrackdown adapters
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
import asyncio
|
|
12
|
+
import json
|
|
13
|
+
import os
|
|
14
|
+
from pathlib import Path
|
|
15
|
+
from typing import Any
|
|
16
|
+
|
|
17
|
+
import typer
|
|
18
|
+
from rich.console import Console
|
|
19
|
+
|
|
20
|
+
from .configure import (
|
|
21
|
+
_configure_aitrackdown,
|
|
22
|
+
_configure_github,
|
|
23
|
+
_configure_jira,
|
|
24
|
+
_configure_linear,
|
|
25
|
+
)
|
|
26
|
+
|
|
27
|
+
console = Console()
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
async def _validate_adapter_credentials(
|
|
31
|
+
adapter_type: str, config_file_path: Path
|
|
32
|
+
) -> list[str]:
|
|
33
|
+
"""Validate adapter credentials by performing real connectivity tests.
|
|
34
|
+
|
|
35
|
+
Args:
|
|
36
|
+
----
|
|
37
|
+
adapter_type: Type of adapter to validate
|
|
38
|
+
config_file_path: Path to config file
|
|
39
|
+
|
|
40
|
+
Returns:
|
|
41
|
+
-------
|
|
42
|
+
List of validation issues (empty if valid)
|
|
43
|
+
|
|
44
|
+
"""
|
|
45
|
+
issues = []
|
|
46
|
+
|
|
47
|
+
try:
|
|
48
|
+
# Load config
|
|
49
|
+
with open(config_file_path) as f:
|
|
50
|
+
config = json.load(f)
|
|
51
|
+
|
|
52
|
+
adapter_config = config.get("adapters", {}).get(adapter_type, {})
|
|
53
|
+
|
|
54
|
+
if not adapter_config:
|
|
55
|
+
issues.append(f"No configuration found for {adapter_type}")
|
|
56
|
+
return issues
|
|
57
|
+
|
|
58
|
+
# Validate based on adapter type
|
|
59
|
+
if adapter_type == "linear":
|
|
60
|
+
api_key = adapter_config.get("api_key")
|
|
61
|
+
|
|
62
|
+
# Check API key format
|
|
63
|
+
if not api_key:
|
|
64
|
+
issues.append("Linear API key is missing")
|
|
65
|
+
return issues
|
|
66
|
+
|
|
67
|
+
if not api_key.startswith("lin_api_"):
|
|
68
|
+
issues.append(
|
|
69
|
+
"Invalid Linear API key format (should start with 'lin_api_')"
|
|
70
|
+
)
|
|
71
|
+
return issues
|
|
72
|
+
|
|
73
|
+
# Test actual connectivity
|
|
74
|
+
try:
|
|
75
|
+
from ..adapters.linear import LinearAdapter
|
|
76
|
+
|
|
77
|
+
adapter = LinearAdapter(adapter_config)
|
|
78
|
+
# Try to list one ticket to verify connectivity
|
|
79
|
+
await adapter.list(limit=1)
|
|
80
|
+
except Exception as e:
|
|
81
|
+
error_msg = str(e)
|
|
82
|
+
if "401" in error_msg or "Unauthorized" in error_msg:
|
|
83
|
+
issues.append(
|
|
84
|
+
"Failed to authenticate with Linear API - invalid API key"
|
|
85
|
+
)
|
|
86
|
+
elif "403" in error_msg or "Forbidden" in error_msg:
|
|
87
|
+
issues.append("Linear API key lacks required permissions")
|
|
88
|
+
elif "team" in error_msg.lower():
|
|
89
|
+
issues.append(f"Linear team configuration error: {error_msg}")
|
|
90
|
+
else:
|
|
91
|
+
issues.append(f"Failed to connect to Linear API: {error_msg}")
|
|
92
|
+
|
|
93
|
+
elif adapter_type == "jira":
|
|
94
|
+
server = adapter_config.get("server")
|
|
95
|
+
email = adapter_config.get("email")
|
|
96
|
+
api_token = adapter_config.get("api_token")
|
|
97
|
+
|
|
98
|
+
# Check required fields
|
|
99
|
+
if not server:
|
|
100
|
+
issues.append("JIRA server URL is missing")
|
|
101
|
+
if not email:
|
|
102
|
+
issues.append("JIRA email is missing")
|
|
103
|
+
if not api_token:
|
|
104
|
+
issues.append("JIRA API token is missing")
|
|
105
|
+
|
|
106
|
+
if issues:
|
|
107
|
+
return issues
|
|
108
|
+
|
|
109
|
+
# Test actual connectivity
|
|
110
|
+
try:
|
|
111
|
+
from ..adapters.jira import JiraAdapter
|
|
112
|
+
|
|
113
|
+
adapter = JiraAdapter(adapter_config)
|
|
114
|
+
await adapter.list(limit=1)
|
|
115
|
+
except Exception as e:
|
|
116
|
+
error_msg = str(e)
|
|
117
|
+
if "401" in error_msg or "Unauthorized" in error_msg:
|
|
118
|
+
issues.append(
|
|
119
|
+
"Failed to authenticate with JIRA - invalid credentials"
|
|
120
|
+
)
|
|
121
|
+
elif "403" in error_msg or "Forbidden" in error_msg:
|
|
122
|
+
issues.append("JIRA credentials lack required permissions")
|
|
123
|
+
else:
|
|
124
|
+
issues.append(f"Failed to connect to JIRA: {error_msg}")
|
|
125
|
+
|
|
126
|
+
elif adapter_type == "github":
|
|
127
|
+
token = adapter_config.get("token") or adapter_config.get("api_key")
|
|
128
|
+
owner = adapter_config.get("owner")
|
|
129
|
+
repo = adapter_config.get("repo")
|
|
130
|
+
|
|
131
|
+
# Check required fields
|
|
132
|
+
if not token:
|
|
133
|
+
issues.append("GitHub token is missing")
|
|
134
|
+
if not owner:
|
|
135
|
+
issues.append("GitHub owner is missing")
|
|
136
|
+
if not repo:
|
|
137
|
+
issues.append("GitHub repo is missing")
|
|
138
|
+
|
|
139
|
+
if issues:
|
|
140
|
+
return issues
|
|
141
|
+
|
|
142
|
+
# Test actual connectivity
|
|
143
|
+
try:
|
|
144
|
+
from ..adapters.github import GitHubAdapter
|
|
145
|
+
|
|
146
|
+
adapter = GitHubAdapter(adapter_config)
|
|
147
|
+
await adapter.list(limit=1)
|
|
148
|
+
except Exception as e:
|
|
149
|
+
error_msg = str(e)
|
|
150
|
+
if (
|
|
151
|
+
"401" in error_msg
|
|
152
|
+
or "Unauthorized" in error_msg
|
|
153
|
+
or "Bad credentials" in error_msg
|
|
154
|
+
):
|
|
155
|
+
issues.append("Failed to authenticate with GitHub - invalid token")
|
|
156
|
+
elif "404" in error_msg or "Not Found" in error_msg:
|
|
157
|
+
issues.append(f"GitHub repository not found: {owner}/{repo}")
|
|
158
|
+
elif "403" in error_msg or "Forbidden" in error_msg:
|
|
159
|
+
issues.append("GitHub token lacks required permissions")
|
|
160
|
+
else:
|
|
161
|
+
issues.append(f"Failed to connect to GitHub: {error_msg}")
|
|
162
|
+
|
|
163
|
+
elif adapter_type == "aitrackdown":
|
|
164
|
+
# AITrackdown doesn't require credentials, just check base_path is set
|
|
165
|
+
base_path = adapter_config.get("base_path")
|
|
166
|
+
if not base_path:
|
|
167
|
+
issues.append("AITrackdown base_path is missing")
|
|
168
|
+
|
|
169
|
+
except Exception as e:
|
|
170
|
+
issues.append(f"Validation error: {str(e)}")
|
|
171
|
+
|
|
172
|
+
return issues
|
|
173
|
+
|
|
174
|
+
|
|
175
|
+
async def _validate_configuration_with_retry(
|
|
176
|
+
console: Console, adapter_type: str, config_file_path: Path, proj_path: Path
|
|
177
|
+
) -> bool:
|
|
178
|
+
"""Validate configuration with retry loop for corrections.
|
|
179
|
+
|
|
180
|
+
Args:
|
|
181
|
+
----
|
|
182
|
+
console: Rich console for output
|
|
183
|
+
adapter_type: Type of adapter configured
|
|
184
|
+
config_file_path: Path to config file
|
|
185
|
+
proj_path: Project path
|
|
186
|
+
|
|
187
|
+
Returns:
|
|
188
|
+
-------
|
|
189
|
+
True if validation passed or user chose to continue, False if user chose to exit
|
|
190
|
+
|
|
191
|
+
"""
|
|
192
|
+
max_retries = 3
|
|
193
|
+
retry_count = 0
|
|
194
|
+
|
|
195
|
+
while retry_count < max_retries:
|
|
196
|
+
console.print("\n[cyan]🔍 Validating configuration...[/cyan]")
|
|
197
|
+
|
|
198
|
+
# Run real adapter validation (suppress verbose output)
|
|
199
|
+
import io
|
|
200
|
+
import sys
|
|
201
|
+
|
|
202
|
+
# Capture output to suppress verbose diagnostics output
|
|
203
|
+
old_stdout = sys.stdout
|
|
204
|
+
old_stderr = sys.stderr
|
|
205
|
+
sys.stdout = io.StringIO()
|
|
206
|
+
sys.stderr = io.StringIO()
|
|
207
|
+
|
|
208
|
+
try:
|
|
209
|
+
# Perform real adapter validation using diagnostics
|
|
210
|
+
validation_issues = await _validate_adapter_credentials(
|
|
211
|
+
adapter_type, config_file_path
|
|
212
|
+
)
|
|
213
|
+
finally:
|
|
214
|
+
# Restore stdout/stderr
|
|
215
|
+
sys.stdout = old_stdout
|
|
216
|
+
sys.stderr = old_stderr
|
|
217
|
+
|
|
218
|
+
# Check if there are issues
|
|
219
|
+
if not validation_issues:
|
|
220
|
+
console.print("[green]✓ Configuration validated successfully![/green]")
|
|
221
|
+
return True
|
|
222
|
+
|
|
223
|
+
# Display issues found
|
|
224
|
+
console.print("[yellow]⚠️ Configuration validation found issues:[/yellow]")
|
|
225
|
+
for issue in validation_issues:
|
|
226
|
+
console.print(f" [red]❌[/red] {issue}")
|
|
227
|
+
|
|
228
|
+
# Offer user options
|
|
229
|
+
console.print("\n[bold]What would you like to do?[/bold]")
|
|
230
|
+
console.print("1. [cyan]Re-enter configuration values[/cyan] (fix issues)")
|
|
231
|
+
console.print("2. [yellow]Continue anyway[/yellow] (skip validation)")
|
|
232
|
+
console.print("3. [red]Exit[/red] (fix manually later)")
|
|
233
|
+
|
|
234
|
+
try:
|
|
235
|
+
choice = typer.prompt("\nSelect option (1-3)", type=int, default=1)
|
|
236
|
+
except typer.Abort:
|
|
237
|
+
console.print("[yellow]Cancelled.[/yellow]")
|
|
238
|
+
return False
|
|
239
|
+
|
|
240
|
+
if choice == 1:
|
|
241
|
+
# Re-enter configuration
|
|
242
|
+
# Check BEFORE increment to fix off-by-one error
|
|
243
|
+
if retry_count >= max_retries:
|
|
244
|
+
console.print(
|
|
245
|
+
f"[red]Maximum retry attempts ({max_retries}) reached.[/red]"
|
|
246
|
+
)
|
|
247
|
+
console.print(
|
|
248
|
+
"[yellow]Please fix configuration manually and run 'mcp-ticketer doctor'[/yellow]"
|
|
249
|
+
)
|
|
250
|
+
return False
|
|
251
|
+
retry_count += 1
|
|
252
|
+
|
|
253
|
+
console.print(
|
|
254
|
+
f"\n[cyan]Retry {retry_count}/{max_retries} - Re-entering configuration...[/cyan]"
|
|
255
|
+
)
|
|
256
|
+
|
|
257
|
+
# Reload current config to get values
|
|
258
|
+
with open(config_file_path) as f:
|
|
259
|
+
current_config = json.load(f)
|
|
260
|
+
|
|
261
|
+
# Re-prompt for adapter-specific configuration using consolidated functions
|
|
262
|
+
try:
|
|
263
|
+
if adapter_type == "linear":
|
|
264
|
+
adapter_config, default_values = _configure_linear(interactive=True)
|
|
265
|
+
current_config["adapters"]["linear"] = adapter_config.to_dict()
|
|
266
|
+
# Merge default values into top-level config
|
|
267
|
+
if default_values.get("default_user"):
|
|
268
|
+
current_config["default_user"] = default_values["default_user"]
|
|
269
|
+
if default_values.get("default_epic"):
|
|
270
|
+
current_config["default_epic"] = default_values["default_epic"]
|
|
271
|
+
if default_values.get("default_project"):
|
|
272
|
+
current_config["default_project"] = default_values[
|
|
273
|
+
"default_project"
|
|
274
|
+
]
|
|
275
|
+
if default_values.get("default_tags"):
|
|
276
|
+
current_config["default_tags"] = default_values["default_tags"]
|
|
277
|
+
|
|
278
|
+
elif adapter_type == "jira":
|
|
279
|
+
# Returns tuple: (AdapterConfig, default_values_dict)
|
|
280
|
+
adapter_config, default_values = _configure_jira(interactive=True)
|
|
281
|
+
current_config["adapters"]["jira"] = adapter_config.to_dict()
|
|
282
|
+
|
|
283
|
+
# Merge default values into top-level config
|
|
284
|
+
if default_values.get("default_user"):
|
|
285
|
+
current_config["default_user"] = default_values["default_user"]
|
|
286
|
+
if default_values.get("default_epic"):
|
|
287
|
+
current_config["default_epic"] = default_values["default_epic"]
|
|
288
|
+
if default_values.get("default_project"):
|
|
289
|
+
current_config["default_project"] = default_values[
|
|
290
|
+
"default_project"
|
|
291
|
+
]
|
|
292
|
+
if default_values.get("default_tags"):
|
|
293
|
+
current_config["default_tags"] = default_values["default_tags"]
|
|
294
|
+
|
|
295
|
+
elif adapter_type == "github":
|
|
296
|
+
# Returns tuple: (AdapterConfig, default_values_dict)
|
|
297
|
+
adapter_config, default_values = _configure_github(interactive=True)
|
|
298
|
+
current_config["adapters"]["github"] = adapter_config.to_dict()
|
|
299
|
+
|
|
300
|
+
# Merge default values into top-level config
|
|
301
|
+
if default_values.get("default_user"):
|
|
302
|
+
current_config["default_user"] = default_values["default_user"]
|
|
303
|
+
if default_values.get("default_epic"):
|
|
304
|
+
current_config["default_epic"] = default_values["default_epic"]
|
|
305
|
+
if default_values.get("default_project"):
|
|
306
|
+
current_config["default_project"] = default_values[
|
|
307
|
+
"default_project"
|
|
308
|
+
]
|
|
309
|
+
if default_values.get("default_tags"):
|
|
310
|
+
current_config["default_tags"] = default_values["default_tags"]
|
|
311
|
+
|
|
312
|
+
elif adapter_type == "aitrackdown":
|
|
313
|
+
# Returns tuple: (AdapterConfig, default_values_dict)
|
|
314
|
+
adapter_config, default_values = _configure_aitrackdown(
|
|
315
|
+
interactive=True
|
|
316
|
+
)
|
|
317
|
+
current_config["adapters"]["aitrackdown"] = adapter_config.to_dict()
|
|
318
|
+
# Save updated configuration
|
|
319
|
+
with open(config_file_path, "w") as f:
|
|
320
|
+
json.dump(current_config, f, indent=2)
|
|
321
|
+
|
|
322
|
+
console.print(
|
|
323
|
+
"[yellow]AITrackdown doesn't require credentials. Continuing...[/yellow]"
|
|
324
|
+
)
|
|
325
|
+
console.print("[dim]✓ Configuration updated[/dim]")
|
|
326
|
+
return True
|
|
327
|
+
|
|
328
|
+
else:
|
|
329
|
+
console.print(f"[red]Unknown adapter type: {adapter_type}[/red]")
|
|
330
|
+
return False
|
|
331
|
+
|
|
332
|
+
except (ValueError, typer.Exit) as e:
|
|
333
|
+
console.print(f"[red]Configuration error: {e}[/red]")
|
|
334
|
+
# Continue to retry loop
|
|
335
|
+
continue
|
|
336
|
+
|
|
337
|
+
# Save updated configuration
|
|
338
|
+
with open(config_file_path, "w") as f:
|
|
339
|
+
json.dump(current_config, f, indent=2)
|
|
340
|
+
|
|
341
|
+
console.print("[dim]✓ Configuration updated[/dim]")
|
|
342
|
+
# Loop will retry validation
|
|
343
|
+
|
|
344
|
+
elif choice == 2:
|
|
345
|
+
# Continue anyway
|
|
346
|
+
console.print(
|
|
347
|
+
"[yellow]⚠️ Continuing with potentially invalid configuration.[/yellow]"
|
|
348
|
+
)
|
|
349
|
+
console.print("[dim]You can validate later with: mcp-ticketer doctor[/dim]")
|
|
350
|
+
return True
|
|
351
|
+
|
|
352
|
+
elif choice == 3:
|
|
353
|
+
# Exit
|
|
354
|
+
console.print(
|
|
355
|
+
"[yellow]Configuration saved but not validated. Run 'mcp-ticketer doctor' to test.[/yellow]"
|
|
356
|
+
)
|
|
357
|
+
return False
|
|
358
|
+
|
|
359
|
+
else:
|
|
360
|
+
console.print(
|
|
361
|
+
f"[red]Invalid choice: {choice}. Please enter 1, 2, or 3.[/red]"
|
|
362
|
+
)
|
|
363
|
+
# Continue loop to ask again
|
|
364
|
+
|
|
365
|
+
return True
|
|
366
|
+
|
|
367
|
+
|
|
368
|
+
def _show_next_steps(
|
|
369
|
+
console: Console, adapter_type: str, config_file_path: Path
|
|
370
|
+
) -> None:
|
|
371
|
+
"""Show helpful next steps after initialization.
|
|
372
|
+
|
|
373
|
+
Args:
|
|
374
|
+
----
|
|
375
|
+
console: Rich console for output
|
|
376
|
+
adapter_type: Type of adapter that was configured
|
|
377
|
+
config_file_path: Path to the configuration file
|
|
378
|
+
|
|
379
|
+
"""
|
|
380
|
+
console.print("\n[bold green]🎉 Setup Complete![/bold green]")
|
|
381
|
+
console.print(f"MCP Ticketer is now configured to use {adapter_type.title()}.\n")
|
|
382
|
+
|
|
383
|
+
console.print("[bold]Next Steps:[/bold]")
|
|
384
|
+
console.print("1. [cyan]Create a test ticket:[/cyan]")
|
|
385
|
+
console.print(" mcp-ticketer create 'Test ticket from MCP Ticketer'")
|
|
386
|
+
|
|
387
|
+
if adapter_type != "aitrackdown":
|
|
388
|
+
console.print(
|
|
389
|
+
f"\n2. [cyan]Verify the ticket appears in {adapter_type.title()}[/cyan]"
|
|
390
|
+
)
|
|
391
|
+
if adapter_type == "linear":
|
|
392
|
+
console.print(" Check your Linear workspace for the new ticket")
|
|
393
|
+
elif adapter_type == "github":
|
|
394
|
+
console.print(" Check your GitHub repository's Issues tab")
|
|
395
|
+
elif adapter_type == "jira":
|
|
396
|
+
console.print(" Check your JIRA project for the new ticket")
|
|
397
|
+
else:
|
|
398
|
+
console.print("\n2. [cyan]Check local ticket storage:[/cyan]")
|
|
399
|
+
console.print(" ls .aitrackdown/")
|
|
400
|
+
|
|
401
|
+
console.print("\n3. [cyan]Install MCP for AI clients (optional):[/cyan]")
|
|
402
|
+
console.print(" mcp-ticketer install claude-code # For Claude Code")
|
|
403
|
+
console.print(" mcp-ticketer install claude-desktop # For Claude Desktop")
|
|
404
|
+
console.print(" mcp-ticketer install auggie # For Auggie")
|
|
405
|
+
console.print(" mcp-ticketer install gemini # For Gemini CLI")
|
|
406
|
+
|
|
407
|
+
console.print(f"\n[dim]Configuration saved to: {config_file_path}[/dim]")
|
|
408
|
+
console.print(
|
|
409
|
+
"[dim]Run 'mcp-ticketer doctor' to re-validate configuration anytime[/dim]"
|
|
410
|
+
)
|
|
411
|
+
console.print("[dim]Run 'mcp-ticketer --help' for more commands[/dim]")
|
|
412
|
+
|
|
413
|
+
|
|
414
|
+
def _init_adapter_internal(
|
|
415
|
+
adapter: str | None = None,
|
|
416
|
+
project_path: str | None = None,
|
|
417
|
+
global_config: bool = False,
|
|
418
|
+
base_path: str | None = None,
|
|
419
|
+
api_key: str | None = None,
|
|
420
|
+
team_id: str | None = None,
|
|
421
|
+
jira_server: str | None = None,
|
|
422
|
+
jira_email: str | None = None,
|
|
423
|
+
jira_project: str | None = None,
|
|
424
|
+
github_url: str | None = None,
|
|
425
|
+
github_token: str | None = None,
|
|
426
|
+
**kwargs: Any,
|
|
427
|
+
) -> bool:
|
|
428
|
+
"""Internal function to initialize adapter configuration.
|
|
429
|
+
|
|
430
|
+
This is the core business logic for adapter initialization, separated from
|
|
431
|
+
the Typer CLI command to allow programmatic calls from setup_command.py.
|
|
432
|
+
|
|
433
|
+
Args:
|
|
434
|
+
----
|
|
435
|
+
adapter: Adapter type to use (interactive prompt if not specified)
|
|
436
|
+
project_path: Project path (default: current directory)
|
|
437
|
+
global_config: Save to global config instead of project-specific
|
|
438
|
+
base_path: Base path for ticket storage (AITrackdown only)
|
|
439
|
+
api_key: API key for Linear or API token for JIRA
|
|
440
|
+
team_id: Linear team ID (required for Linear adapter)
|
|
441
|
+
jira_server: JIRA server URL
|
|
442
|
+
jira_email: JIRA user email for authentication
|
|
443
|
+
jira_project: Default JIRA project key
|
|
444
|
+
github_url: GitHub repository URL (e.g., https://github.com/owner/repo)
|
|
445
|
+
github_token: GitHub Personal Access Token
|
|
446
|
+
**kwargs: Additional parameters (includes deprecated github_owner, github_repo for backward compatibility)
|
|
447
|
+
|
|
448
|
+
Returns:
|
|
449
|
+
-------
|
|
450
|
+
True if initialization succeeded, False otherwise
|
|
451
|
+
|
|
452
|
+
"""
|
|
453
|
+
from ..core.env_discovery import discover_config
|
|
454
|
+
|
|
455
|
+
# Determine project path
|
|
456
|
+
proj_path = Path(project_path) if project_path else Path.cwd()
|
|
457
|
+
|
|
458
|
+
# Check if already initialized (unless using --global)
|
|
459
|
+
# Note: This check is skipped when called programmatically
|
|
460
|
+
# Callers should handle overwrite confirmation themselves
|
|
461
|
+
|
|
462
|
+
# 1. Try auto-discovery if no adapter specified
|
|
463
|
+
discovered = None
|
|
464
|
+
adapter_type = adapter
|
|
465
|
+
|
|
466
|
+
if not adapter_type:
|
|
467
|
+
console.print(
|
|
468
|
+
"[cyan]🔍 Auto-discovering configuration from .env files...[/cyan]"
|
|
469
|
+
)
|
|
470
|
+
|
|
471
|
+
# First try our improved .env configuration loader
|
|
472
|
+
from ..mcp.server.main import _load_env_configuration
|
|
473
|
+
|
|
474
|
+
env_config = _load_env_configuration()
|
|
475
|
+
|
|
476
|
+
if env_config:
|
|
477
|
+
adapter_type = env_config["adapter_type"]
|
|
478
|
+
console.print(
|
|
479
|
+
f"[green]✓ Detected {adapter_type} adapter from environment files[/green]"
|
|
480
|
+
)
|
|
481
|
+
|
|
482
|
+
# Show what was discovered
|
|
483
|
+
console.print("\n[dim]Configuration found in: .env files[/dim]")
|
|
484
|
+
console.print("[dim]Confidence: 100%[/dim]")
|
|
485
|
+
|
|
486
|
+
# Use auto-detected adapter in programmatic mode
|
|
487
|
+
# Interactive mode will be handled by the CLI wrapper
|
|
488
|
+
# For programmatic calls, we accept the detected adapter
|
|
489
|
+
else:
|
|
490
|
+
# Fallback to old discovery system for backward compatibility
|
|
491
|
+
discovered = discover_config(proj_path)
|
|
492
|
+
|
|
493
|
+
if discovered and discovered.adapters:
|
|
494
|
+
primary = discovered.get_primary_adapter()
|
|
495
|
+
if primary:
|
|
496
|
+
adapter_type = primary.adapter_type
|
|
497
|
+
console.print(
|
|
498
|
+
f"[green]✓ Detected {adapter_type} adapter from environment files[/green]"
|
|
499
|
+
)
|
|
500
|
+
|
|
501
|
+
# Show what was discovered
|
|
502
|
+
console.print(
|
|
503
|
+
f"\n[dim]Configuration found in: {primary.found_in}[/dim]"
|
|
504
|
+
)
|
|
505
|
+
console.print(f"[dim]Confidence: {primary.confidence:.0%}[/dim]")
|
|
506
|
+
|
|
507
|
+
# Use auto-detected adapter in programmatic mode
|
|
508
|
+
# Interactive confirmation will be handled by the CLI wrapper
|
|
509
|
+
else:
|
|
510
|
+
adapter_type = None # Will trigger interactive selection
|
|
511
|
+
else:
|
|
512
|
+
adapter_type = None # Will trigger interactive selection
|
|
513
|
+
|
|
514
|
+
# If no adapter determined, fail in programmatic mode
|
|
515
|
+
# (interactive selection will be handled by CLI wrapper)
|
|
516
|
+
if not adapter_type:
|
|
517
|
+
console.print(
|
|
518
|
+
"[red]Error: Could not determine adapter type. "
|
|
519
|
+
"Please specify --adapter or set environment variables.[/red]"
|
|
520
|
+
)
|
|
521
|
+
return False
|
|
522
|
+
|
|
523
|
+
# 2. Create configuration based on adapter type
|
|
524
|
+
config = {"default_adapter": adapter_type, "adapters": {}}
|
|
525
|
+
|
|
526
|
+
# 3. If discovered and matches adapter_type, use discovered config
|
|
527
|
+
if discovered and adapter_type != "aitrackdown":
|
|
528
|
+
discovered_adapter = discovered.get_adapter_by_type(adapter_type)
|
|
529
|
+
if discovered_adapter:
|
|
530
|
+
adapter_config = discovered_adapter.config.copy()
|
|
531
|
+
# Ensure the config has the correct 'type' field
|
|
532
|
+
adapter_config["type"] = adapter_type
|
|
533
|
+
# Remove 'adapter' field if present (legacy)
|
|
534
|
+
adapter_config.pop("adapter", None)
|
|
535
|
+
config["adapters"][adapter_type] = adapter_config
|
|
536
|
+
|
|
537
|
+
# 4. Handle manual configuration for specific adapters
|
|
538
|
+
if adapter_type == "aitrackdown":
|
|
539
|
+
config["adapters"]["aitrackdown"] = {
|
|
540
|
+
"type": "aitrackdown",
|
|
541
|
+
"base_path": base_path or ".aitrackdown",
|
|
542
|
+
}
|
|
543
|
+
|
|
544
|
+
elif adapter_type == "linear":
|
|
545
|
+
# If not auto-discovered, build from CLI params or use consolidated function
|
|
546
|
+
if adapter_type not in config["adapters"]:
|
|
547
|
+
try:
|
|
548
|
+
# Determine if we need interactive prompts
|
|
549
|
+
has_all_params = bool(
|
|
550
|
+
(api_key or os.getenv("LINEAR_API_KEY"))
|
|
551
|
+
and (
|
|
552
|
+
team_id
|
|
553
|
+
or os.getenv("LINEAR_TEAM_ID")
|
|
554
|
+
or os.getenv("LINEAR_TEAM_KEY")
|
|
555
|
+
)
|
|
556
|
+
)
|
|
557
|
+
|
|
558
|
+
# Use consolidated configure function (interactive if missing params)
|
|
559
|
+
adapter_config, default_values = _configure_linear(
|
|
560
|
+
interactive=not has_all_params,
|
|
561
|
+
api_key=api_key,
|
|
562
|
+
team_id=team_id,
|
|
563
|
+
)
|
|
564
|
+
|
|
565
|
+
config["adapters"]["linear"] = adapter_config.to_dict()
|
|
566
|
+
|
|
567
|
+
# Merge default values into top-level config
|
|
568
|
+
if default_values.get("default_user"):
|
|
569
|
+
config["default_user"] = default_values["default_user"]
|
|
570
|
+
if default_values.get("default_epic"):
|
|
571
|
+
config["default_epic"] = default_values["default_epic"]
|
|
572
|
+
if default_values.get("default_project"):
|
|
573
|
+
config["default_project"] = default_values["default_project"]
|
|
574
|
+
if default_values.get("default_tags"):
|
|
575
|
+
config["default_tags"] = default_values["default_tags"]
|
|
576
|
+
|
|
577
|
+
except ValueError as e:
|
|
578
|
+
console.print(f"[red]Error:[/red] {e}")
|
|
579
|
+
return False
|
|
580
|
+
|
|
581
|
+
elif adapter_type == "jira":
|
|
582
|
+
# If not auto-discovered, build from CLI params or use consolidated function
|
|
583
|
+
if adapter_type not in config["adapters"]:
|
|
584
|
+
try:
|
|
585
|
+
# Determine if we need interactive prompts
|
|
586
|
+
has_all_params = bool(
|
|
587
|
+
(jira_server or os.getenv("JIRA_SERVER"))
|
|
588
|
+
and (jira_email or os.getenv("JIRA_EMAIL"))
|
|
589
|
+
and (api_key or os.getenv("JIRA_API_TOKEN"))
|
|
590
|
+
)
|
|
591
|
+
|
|
592
|
+
# Use consolidated configure function (interactive if missing params)
|
|
593
|
+
# Returns tuple: (AdapterConfig, default_values_dict) - following Linear pattern
|
|
594
|
+
adapter_config, default_values = _configure_jira(
|
|
595
|
+
interactive=not has_all_params,
|
|
596
|
+
server=jira_server,
|
|
597
|
+
email=jira_email,
|
|
598
|
+
api_token=api_key,
|
|
599
|
+
project_key=jira_project,
|
|
600
|
+
)
|
|
601
|
+
|
|
602
|
+
config["adapters"]["jira"] = adapter_config.to_dict()
|
|
603
|
+
|
|
604
|
+
# Merge default values into top-level config
|
|
605
|
+
if default_values.get("default_user"):
|
|
606
|
+
config["default_user"] = default_values["default_user"]
|
|
607
|
+
if default_values.get("default_epic"):
|
|
608
|
+
config["default_epic"] = default_values["default_epic"]
|
|
609
|
+
if default_values.get("default_project"):
|
|
610
|
+
config["default_project"] = default_values["default_project"]
|
|
611
|
+
if default_values.get("default_tags"):
|
|
612
|
+
config["default_tags"] = default_values["default_tags"]
|
|
613
|
+
|
|
614
|
+
except ValueError as e:
|
|
615
|
+
console.print(f"[red]Error:[/red] {e}")
|
|
616
|
+
return False
|
|
617
|
+
|
|
618
|
+
elif adapter_type == "github":
|
|
619
|
+
# If not auto-discovered, build from CLI params or use consolidated function
|
|
620
|
+
if adapter_type not in config["adapters"]:
|
|
621
|
+
try:
|
|
622
|
+
# Extract deprecated parameters for backward compatibility
|
|
623
|
+
github_owner = kwargs.get("github_owner")
|
|
624
|
+
github_repo = kwargs.get("github_repo")
|
|
625
|
+
|
|
626
|
+
# Determine if we need interactive prompts
|
|
627
|
+
# Prioritize github_url, fallback to owner/repo
|
|
628
|
+
has_all_params = bool(
|
|
629
|
+
(
|
|
630
|
+
github_url
|
|
631
|
+
or os.getenv("GITHUB_REPO_URL")
|
|
632
|
+
or (github_owner or os.getenv("GITHUB_OWNER"))
|
|
633
|
+
and (github_repo or os.getenv("GITHUB_REPO"))
|
|
634
|
+
)
|
|
635
|
+
and (github_token or os.getenv("GITHUB_TOKEN"))
|
|
636
|
+
)
|
|
637
|
+
|
|
638
|
+
# Use consolidated configure function (interactive if missing params)
|
|
639
|
+
# Returns tuple: (AdapterConfig, default_values_dict) - following Linear pattern
|
|
640
|
+
adapter_config, default_values = _configure_github(
|
|
641
|
+
interactive=not has_all_params,
|
|
642
|
+
repo_url=github_url,
|
|
643
|
+
owner=github_owner,
|
|
644
|
+
repo=github_repo,
|
|
645
|
+
token=github_token,
|
|
646
|
+
)
|
|
647
|
+
|
|
648
|
+
config["adapters"]["github"] = adapter_config.to_dict()
|
|
649
|
+
|
|
650
|
+
# Merge default values into top-level config
|
|
651
|
+
if default_values.get("default_user"):
|
|
652
|
+
config["default_user"] = default_values["default_user"]
|
|
653
|
+
if default_values.get("default_epic"):
|
|
654
|
+
config["default_epic"] = default_values["default_epic"]
|
|
655
|
+
if default_values.get("default_project"):
|
|
656
|
+
config["default_project"] = default_values["default_project"]
|
|
657
|
+
if default_values.get("default_tags"):
|
|
658
|
+
config["default_tags"] = default_values["default_tags"]
|
|
659
|
+
|
|
660
|
+
except ValueError as e:
|
|
661
|
+
console.print(f"[red]Error:[/red] {e}")
|
|
662
|
+
return False
|
|
663
|
+
|
|
664
|
+
# 5. Save to project-local config (global config deprecated for security)
|
|
665
|
+
# Always save to ./.mcp-ticketer/config.json (PROJECT-SPECIFIC)
|
|
666
|
+
config_file_path = proj_path / ".mcp-ticketer" / "config.json"
|
|
667
|
+
config_file_path.parent.mkdir(parents=True, exist_ok=True)
|
|
668
|
+
|
|
669
|
+
with open(config_file_path, "w") as f:
|
|
670
|
+
json.dump(config, f, indent=2)
|
|
671
|
+
|
|
672
|
+
if global_config:
|
|
673
|
+
console.print(
|
|
674
|
+
"[yellow]Note: Global config deprecated for security. Saved to project config instead.[/yellow]"
|
|
675
|
+
)
|
|
676
|
+
|
|
677
|
+
console.print(f"[green]✓ Initialized with {adapter_type} adapter[/green]")
|
|
678
|
+
console.print(f"[dim]Project configuration saved to {config_file_path}[/dim]")
|
|
679
|
+
|
|
680
|
+
# Add .mcp-ticketer to .gitignore if not already there
|
|
681
|
+
gitignore_path = proj_path / ".gitignore"
|
|
682
|
+
if gitignore_path.exists():
|
|
683
|
+
gitignore_content = gitignore_path.read_text()
|
|
684
|
+
if ".mcp-ticketer" not in gitignore_content:
|
|
685
|
+
with open(gitignore_path, "a") as f:
|
|
686
|
+
f.write("\n# MCP Ticketer\n.mcp-ticketer/\n")
|
|
687
|
+
console.print("[dim]✓ Added .mcp-ticketer/ to .gitignore[/dim]")
|
|
688
|
+
else:
|
|
689
|
+
# Create .gitignore if it doesn't exist
|
|
690
|
+
with open(gitignore_path, "w") as f:
|
|
691
|
+
f.write("# MCP Ticketer\n.mcp-ticketer/\n")
|
|
692
|
+
console.print("[dim]✓ Created .gitignore with .mcp-ticketer/[/dim]")
|
|
693
|
+
|
|
694
|
+
# Validate configuration with loop for corrections
|
|
695
|
+
if not asyncio.run(
|
|
696
|
+
_validate_configuration_with_retry(
|
|
697
|
+
console, adapter_type, config_file_path, proj_path
|
|
698
|
+
)
|
|
699
|
+
):
|
|
700
|
+
# User chose to exit without valid configuration
|
|
701
|
+
return False
|
|
702
|
+
|
|
703
|
+
# Show next steps
|
|
704
|
+
_show_next_steps(console, adapter_type, config_file_path)
|
|
705
|
+
return True
|
|
706
|
+
|
|
707
|
+
|
|
708
|
+
def init(
|
|
709
|
+
adapter: str | None = typer.Option(
|
|
710
|
+
None,
|
|
711
|
+
"--adapter",
|
|
712
|
+
"-a",
|
|
713
|
+
help="Adapter type to use (interactive prompt if not specified)",
|
|
714
|
+
),
|
|
715
|
+
project_path: str | None = typer.Option(
|
|
716
|
+
None, "--path", help="Project path (default: current directory)"
|
|
717
|
+
),
|
|
718
|
+
global_config: bool = typer.Option(
|
|
719
|
+
False,
|
|
720
|
+
"--global",
|
|
721
|
+
"-g",
|
|
722
|
+
help="Save to global config instead of project-specific",
|
|
723
|
+
),
|
|
724
|
+
base_path: str | None = typer.Option(
|
|
725
|
+
None,
|
|
726
|
+
"--base-path",
|
|
727
|
+
"-p",
|
|
728
|
+
help="Base path for ticket storage (AITrackdown only)",
|
|
729
|
+
),
|
|
730
|
+
api_key: str | None = typer.Option(
|
|
731
|
+
None, "--api-key", help="API key for Linear or API token for JIRA"
|
|
732
|
+
),
|
|
733
|
+
team_id: str | None = typer.Option(
|
|
734
|
+
None, "--team-id", help="Linear team ID (required for Linear adapter)"
|
|
735
|
+
),
|
|
736
|
+
jira_server: str | None = typer.Option(
|
|
737
|
+
None,
|
|
738
|
+
"--jira-server",
|
|
739
|
+
help="JIRA server URL (e.g., https://company.atlassian.net)",
|
|
740
|
+
),
|
|
741
|
+
jira_email: str | None = typer.Option(
|
|
742
|
+
None, "--jira-email", help="JIRA user email for authentication"
|
|
743
|
+
),
|
|
744
|
+
jira_project: str | None = typer.Option(
|
|
745
|
+
None, "--jira-project", help="Default JIRA project key"
|
|
746
|
+
),
|
|
747
|
+
github_url: str | None = typer.Option(
|
|
748
|
+
None,
|
|
749
|
+
"--github-url",
|
|
750
|
+
help="GitHub repository URL (e.g., https://github.com/owner/repo)",
|
|
751
|
+
),
|
|
752
|
+
github_token: str | None = typer.Option(
|
|
753
|
+
None, "--github-token", help="GitHub Personal Access Token"
|
|
754
|
+
),
|
|
755
|
+
# Deprecated parameters for backward compatibility (hidden from help)
|
|
756
|
+
github_owner: str | None = typer.Option(None, "--github-owner", hidden=True),
|
|
757
|
+
github_repo: str | None = typer.Option(None, "--github-repo", hidden=True),
|
|
758
|
+
) -> None:
|
|
759
|
+
"""Initialize adapter configuration only (without platform installation).
|
|
760
|
+
|
|
761
|
+
This command sets up adapter configuration with interactive prompts.
|
|
762
|
+
It auto-detects adapter configuration from .env files or prompts for
|
|
763
|
+
interactive setup if no configuration is found.
|
|
764
|
+
|
|
765
|
+
Creates .mcp-ticketer/config.json in the current directory.
|
|
766
|
+
|
|
767
|
+
RECOMMENDED: Use 'mcp-ticketer setup' instead for a complete setup
|
|
768
|
+
experience that includes both adapter configuration and platform
|
|
769
|
+
installation in one command.
|
|
770
|
+
|
|
771
|
+
The init command automatically validates your configuration after setup:
|
|
772
|
+
- If validation passes, setup completes
|
|
773
|
+
- If issues are detected, you can re-enter credentials, continue anyway, or exit
|
|
774
|
+
- You get up to 3 retry attempts to fix configuration issues
|
|
775
|
+
- You can always re-validate later with 'mcp-ticketer doctor'
|
|
776
|
+
|
|
777
|
+
Examples:
|
|
778
|
+
--------
|
|
779
|
+
# For first-time setup, use 'setup' instead (recommended)
|
|
780
|
+
mcp-ticketer setup
|
|
781
|
+
|
|
782
|
+
# Initialize adapter only (advanced usage)
|
|
783
|
+
mcp-ticketer init
|
|
784
|
+
|
|
785
|
+
# Force specific adapter
|
|
786
|
+
mcp-ticketer init --adapter linear
|
|
787
|
+
|
|
788
|
+
# Initialize for different project
|
|
789
|
+
mcp-ticketer init --path /path/to/project
|
|
790
|
+
|
|
791
|
+
"""
|
|
792
|
+
from .setup_command import _prompt_for_adapter_selection
|
|
793
|
+
|
|
794
|
+
# Determine project path
|
|
795
|
+
proj_path = Path(project_path) if project_path else Path.cwd()
|
|
796
|
+
|
|
797
|
+
# Check if already initialized (unless using --global)
|
|
798
|
+
if not global_config:
|
|
799
|
+
config_path = proj_path / ".mcp-ticketer" / "config.json"
|
|
800
|
+
|
|
801
|
+
if config_path.exists():
|
|
802
|
+
if not typer.confirm(
|
|
803
|
+
f"Configuration already exists at {config_path}. Overwrite?",
|
|
804
|
+
default=False,
|
|
805
|
+
):
|
|
806
|
+
console.print("[yellow]Initialization cancelled.[/yellow]")
|
|
807
|
+
raise typer.Exit(0) from None
|
|
808
|
+
|
|
809
|
+
# Handle interactive adapter selection if needed
|
|
810
|
+
adapter_type = adapter
|
|
811
|
+
if not adapter_type:
|
|
812
|
+
# Try auto-discovery first
|
|
813
|
+
console.print(
|
|
814
|
+
"[cyan]🔍 Auto-discovering configuration from .env files...[/cyan]"
|
|
815
|
+
)
|
|
816
|
+
|
|
817
|
+
from ..core.env_discovery import discover_config
|
|
818
|
+
from ..mcp.server.main import _load_env_configuration
|
|
819
|
+
|
|
820
|
+
env_config = _load_env_configuration()
|
|
821
|
+
|
|
822
|
+
if env_config:
|
|
823
|
+
adapter_type = env_config["adapter_type"]
|
|
824
|
+
console.print(
|
|
825
|
+
f"[green]✓ Detected {adapter_type} adapter from environment files[/green]"
|
|
826
|
+
)
|
|
827
|
+
console.print("\n[dim]Configuration found in: .env files[/dim]")
|
|
828
|
+
console.print("[dim]Confidence: 100%[/dim]")
|
|
829
|
+
|
|
830
|
+
# Ask user to confirm auto-detected adapter
|
|
831
|
+
if not typer.confirm(
|
|
832
|
+
f"Use detected {adapter_type} adapter?",
|
|
833
|
+
default=True,
|
|
834
|
+
):
|
|
835
|
+
adapter_type = None
|
|
836
|
+
else:
|
|
837
|
+
# Fallback to old discovery
|
|
838
|
+
discovered = discover_config(proj_path)
|
|
839
|
+
if discovered and discovered.adapters:
|
|
840
|
+
primary = discovered.get_primary_adapter()
|
|
841
|
+
if primary:
|
|
842
|
+
adapter_type = primary.adapter_type
|
|
843
|
+
console.print(
|
|
844
|
+
f"[green]✓ Detected {adapter_type} adapter from environment files[/green]"
|
|
845
|
+
)
|
|
846
|
+
console.print(
|
|
847
|
+
f"\n[dim]Configuration found in: {primary.found_in}[/dim]"
|
|
848
|
+
)
|
|
849
|
+
console.print(f"[dim]Confidence: {primary.confidence:.0%}[/dim]")
|
|
850
|
+
|
|
851
|
+
if not typer.confirm(
|
|
852
|
+
f"Use detected {adapter_type} adapter?",
|
|
853
|
+
default=True,
|
|
854
|
+
):
|
|
855
|
+
adapter_type = None
|
|
856
|
+
|
|
857
|
+
# If still no adapter, show interactive selection
|
|
858
|
+
if not adapter_type:
|
|
859
|
+
adapter_type = _prompt_for_adapter_selection(console)
|
|
860
|
+
|
|
861
|
+
# Call internal function with extracted values
|
|
862
|
+
success = _init_adapter_internal(
|
|
863
|
+
adapter=adapter_type,
|
|
864
|
+
project_path=project_path,
|
|
865
|
+
global_config=global_config,
|
|
866
|
+
base_path=base_path,
|
|
867
|
+
api_key=api_key,
|
|
868
|
+
team_id=team_id,
|
|
869
|
+
jira_server=jira_server,
|
|
870
|
+
jira_email=jira_email,
|
|
871
|
+
jira_project=jira_project,
|
|
872
|
+
github_url=github_url,
|
|
873
|
+
github_token=github_token,
|
|
874
|
+
# Pass deprecated parameters via kwargs for backward compatibility
|
|
875
|
+
github_owner=github_owner,
|
|
876
|
+
github_repo=github_repo,
|
|
877
|
+
)
|
|
878
|
+
|
|
879
|
+
if not success:
|
|
880
|
+
raise typer.Exit(1) from None
|