mcp-ticketer 0.3.5__py3-none-any.whl → 0.12.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of mcp-ticketer might be problematic. Click here for more details.
- mcp_ticketer/__version__.py +3 -3
- mcp_ticketer/adapters/__init__.py +2 -0
- mcp_ticketer/adapters/aitrackdown.py +263 -14
- mcp_ticketer/adapters/asana/__init__.py +15 -0
- mcp_ticketer/adapters/asana/adapter.py +1308 -0
- mcp_ticketer/adapters/asana/client.py +292 -0
- mcp_ticketer/adapters/asana/mappers.py +334 -0
- mcp_ticketer/adapters/asana/types.py +146 -0
- mcp_ticketer/adapters/github.py +326 -109
- mcp_ticketer/adapters/hybrid.py +11 -11
- mcp_ticketer/adapters/jira.py +271 -25
- mcp_ticketer/adapters/linear/adapter.py +693 -39
- mcp_ticketer/adapters/linear/client.py +61 -9
- mcp_ticketer/adapters/linear/mappers.py +9 -3
- mcp_ticketer/adapters/linear/queries.py +9 -7
- mcp_ticketer/cache/memory.py +9 -8
- mcp_ticketer/cli/adapter_diagnostics.py +1 -1
- mcp_ticketer/cli/auggie_configure.py +104 -15
- mcp_ticketer/cli/codex_configure.py +188 -32
- mcp_ticketer/cli/configure.py +37 -48
- mcp_ticketer/cli/diagnostics.py +20 -18
- mcp_ticketer/cli/discover.py +292 -26
- mcp_ticketer/cli/gemini_configure.py +107 -26
- mcp_ticketer/cli/instruction_commands.py +429 -0
- mcp_ticketer/cli/linear_commands.py +105 -22
- mcp_ticketer/cli/main.py +1830 -435
- mcp_ticketer/cli/mcp_configure.py +296 -89
- mcp_ticketer/cli/migrate_config.py +12 -8
- mcp_ticketer/cli/platform_commands.py +123 -0
- mcp_ticketer/cli/platform_detection.py +412 -0
- mcp_ticketer/cli/python_detection.py +126 -0
- mcp_ticketer/cli/queue_commands.py +15 -15
- mcp_ticketer/cli/simple_health.py +1 -1
- mcp_ticketer/cli/ticket_commands.py +773 -0
- mcp_ticketer/cli/update_checker.py +313 -0
- mcp_ticketer/cli/utils.py +67 -62
- mcp_ticketer/core/__init__.py +14 -1
- mcp_ticketer/core/adapter.py +84 -15
- mcp_ticketer/core/config.py +44 -39
- mcp_ticketer/core/env_discovery.py +42 -12
- mcp_ticketer/core/env_loader.py +15 -14
- mcp_ticketer/core/exceptions.py +3 -3
- mcp_ticketer/core/http_client.py +26 -26
- mcp_ticketer/core/instructions.py +405 -0
- mcp_ticketer/core/mappers.py +11 -11
- mcp_ticketer/core/models.py +50 -20
- mcp_ticketer/core/onepassword_secrets.py +379 -0
- mcp_ticketer/core/project_config.py +57 -35
- mcp_ticketer/core/registry.py +3 -3
- mcp_ticketer/defaults/ticket_instructions.md +644 -0
- mcp_ticketer/mcp/__init__.py +29 -1
- mcp_ticketer/mcp/__main__.py +60 -0
- mcp_ticketer/mcp/server/__init__.py +25 -0
- mcp_ticketer/mcp/server/__main__.py +60 -0
- mcp_ticketer/mcp/{dto.py → server/dto.py} +32 -32
- mcp_ticketer/mcp/{server.py → server/main.py} +127 -74
- mcp_ticketer/mcp/{response_builder.py → server/response_builder.py} +2 -2
- mcp_ticketer/mcp/server/server_sdk.py +93 -0
- mcp_ticketer/mcp/server/tools/__init__.py +47 -0
- mcp_ticketer/mcp/server/tools/attachment_tools.py +226 -0
- mcp_ticketer/mcp/server/tools/bulk_tools.py +273 -0
- mcp_ticketer/mcp/server/tools/comment_tools.py +90 -0
- mcp_ticketer/mcp/server/tools/config_tools.py +381 -0
- mcp_ticketer/mcp/server/tools/hierarchy_tools.py +532 -0
- mcp_ticketer/mcp/server/tools/instruction_tools.py +293 -0
- mcp_ticketer/mcp/server/tools/pr_tools.py +154 -0
- mcp_ticketer/mcp/server/tools/search_tools.py +206 -0
- mcp_ticketer/mcp/server/tools/ticket_tools.py +430 -0
- mcp_ticketer/mcp/server/tools/user_ticket_tools.py +382 -0
- mcp_ticketer/queue/__init__.py +1 -0
- mcp_ticketer/queue/health_monitor.py +5 -4
- mcp_ticketer/queue/manager.py +15 -51
- mcp_ticketer/queue/queue.py +19 -19
- mcp_ticketer/queue/run_worker.py +1 -1
- mcp_ticketer/queue/ticket_registry.py +14 -14
- mcp_ticketer/queue/worker.py +16 -14
- {mcp_ticketer-0.3.5.dist-info → mcp_ticketer-0.12.0.dist-info}/METADATA +168 -32
- mcp_ticketer-0.12.0.dist-info/RECORD +91 -0
- mcp_ticketer-0.3.5.dist-info/RECORD +0 -62
- /mcp_ticketer/mcp/{constants.py → server/constants.py} +0 -0
- {mcp_ticketer-0.3.5.dist-info → mcp_ticketer-0.12.0.dist-info}/WHEEL +0 -0
- {mcp_ticketer-0.3.5.dist-info → mcp_ticketer-0.12.0.dist-info}/entry_points.txt +0 -0
- {mcp_ticketer-0.3.5.dist-info → mcp_ticketer-0.12.0.dist-info}/licenses/LICENSE +0 -0
- {mcp_ticketer-0.3.5.dist-info → mcp_ticketer-0.12.0.dist-info}/top_level.txt +0 -0
mcp_ticketer/cli/discover.py
CHANGED
|
@@ -1,12 +1,18 @@
|
|
|
1
1
|
"""CLI command for auto-discovering configuration from .env files."""
|
|
2
2
|
|
|
3
3
|
from pathlib import Path
|
|
4
|
-
from typing import Optional
|
|
5
4
|
|
|
6
5
|
import typer
|
|
7
6
|
from rich.console import Console
|
|
7
|
+
from rich.panel import Panel
|
|
8
|
+
from rich.table import Table
|
|
8
9
|
|
|
9
10
|
from ..core.env_discovery import DiscoveredAdapter, EnvDiscovery
|
|
11
|
+
from ..core.onepassword_secrets import (
|
|
12
|
+
OnePasswordConfig,
|
|
13
|
+
OnePasswordSecretsLoader,
|
|
14
|
+
check_op_cli_status,
|
|
15
|
+
)
|
|
10
16
|
from ..core.project_config import (
|
|
11
17
|
AdapterConfig,
|
|
12
18
|
ConfigResolver,
|
|
@@ -93,7 +99,7 @@ def _display_discovered_adapter(
|
|
|
93
99
|
|
|
94
100
|
@app.command()
|
|
95
101
|
def show(
|
|
96
|
-
project_path:
|
|
102
|
+
project_path: Path | None = typer.Option(
|
|
97
103
|
None,
|
|
98
104
|
"--path",
|
|
99
105
|
"-p",
|
|
@@ -148,7 +154,7 @@ def show(
|
|
|
148
154
|
|
|
149
155
|
@app.command()
|
|
150
156
|
def save(
|
|
151
|
-
adapter:
|
|
157
|
+
adapter: str | None = typer.Option(
|
|
152
158
|
None, "--adapter", "-a", help="Which adapter to save (defaults to recommended)"
|
|
153
159
|
),
|
|
154
160
|
global_config: bool = typer.Option(
|
|
@@ -157,7 +163,7 @@ def save(
|
|
|
157
163
|
dry_run: bool = typer.Option(
|
|
158
164
|
False, "--dry-run", help="Show what would be saved without saving"
|
|
159
165
|
),
|
|
160
|
-
project_path:
|
|
166
|
+
project_path: Path | None = typer.Option(
|
|
161
167
|
None,
|
|
162
168
|
"--path",
|
|
163
169
|
"-p",
|
|
@@ -182,7 +188,7 @@ def save(
|
|
|
182
188
|
console.print(
|
|
183
189
|
"[dim]Make sure your .env file contains adapter credentials[/dim]"
|
|
184
190
|
)
|
|
185
|
-
raise typer.Exit(1)
|
|
191
|
+
raise typer.Exit(1) from None
|
|
186
192
|
|
|
187
193
|
# Determine which adapter to save
|
|
188
194
|
if adapter:
|
|
@@ -192,13 +198,13 @@ def save(
|
|
|
192
198
|
console.print(
|
|
193
199
|
f"[dim]Available: {', '.join(a.adapter_type for a in result.adapters)}[/dim]"
|
|
194
200
|
)
|
|
195
|
-
raise typer.Exit(1)
|
|
201
|
+
raise typer.Exit(1) from None
|
|
196
202
|
else:
|
|
197
203
|
# Use recommended adapter
|
|
198
204
|
discovered_adapter = result.get_primary_adapter()
|
|
199
205
|
if not discovered_adapter:
|
|
200
206
|
console.print("[red]Could not determine recommended adapter[/red]")
|
|
201
|
-
raise typer.Exit(1)
|
|
207
|
+
raise typer.Exit(1) from None
|
|
202
208
|
|
|
203
209
|
console.print(
|
|
204
210
|
f"[bold]Using recommended adapter:[/bold] {discovered_adapter.adapter_type}"
|
|
@@ -217,7 +223,7 @@ def save(
|
|
|
217
223
|
console.print(
|
|
218
224
|
"[dim]Fix the configuration in your .env file and try again[/dim]"
|
|
219
225
|
)
|
|
220
|
-
raise typer.Exit(1)
|
|
226
|
+
raise typer.Exit(1) from None
|
|
221
227
|
|
|
222
228
|
if dry_run:
|
|
223
229
|
console.print("\n[yellow]Dry run - no changes made[/yellow]")
|
|
@@ -240,14 +246,15 @@ def save(
|
|
|
240
246
|
# Add to config
|
|
241
247
|
config.adapters[discovered_adapter.adapter_type] = adapter_config
|
|
242
248
|
|
|
243
|
-
# Save
|
|
249
|
+
# Save (always to project config for security)
|
|
244
250
|
try:
|
|
251
|
+
resolver.save_project_config(config, proj_path)
|
|
252
|
+
config_location = proj_path / resolver.PROJECT_CONFIG_SUBPATH
|
|
253
|
+
|
|
245
254
|
if global_config:
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
resolver.save_project_config(config, proj_path)
|
|
250
|
-
config_location = proj_path / resolver.PROJECT_CONFIG_SUBPATH
|
|
255
|
+
console.print(
|
|
256
|
+
"[yellow]Note: Global config deprecated for security. Saved to project config instead.[/yellow]"
|
|
257
|
+
)
|
|
251
258
|
|
|
252
259
|
console.print(f"\n[green]✅ Configuration saved to:[/green] {config_location}")
|
|
253
260
|
console.print(
|
|
@@ -256,12 +263,12 @@ def save(
|
|
|
256
263
|
|
|
257
264
|
except Exception as e:
|
|
258
265
|
console.print(f"\n[red]Failed to save configuration:[/red] {e}")
|
|
259
|
-
raise typer.Exit(1)
|
|
266
|
+
raise typer.Exit(1) from None
|
|
260
267
|
|
|
261
268
|
|
|
262
269
|
@app.command()
|
|
263
270
|
def interactive(
|
|
264
|
-
project_path:
|
|
271
|
+
project_path: Path | None = typer.Option(
|
|
265
272
|
None,
|
|
266
273
|
"--path",
|
|
267
274
|
"-p",
|
|
@@ -284,7 +291,7 @@ def interactive(
|
|
|
284
291
|
console.print(f" ✅ {env_file}")
|
|
285
292
|
else:
|
|
286
293
|
console.print("[red]No .env files found[/red]")
|
|
287
|
-
raise typer.Exit(1)
|
|
294
|
+
raise typer.Exit(1) from None
|
|
288
295
|
|
|
289
296
|
# Show discovered adapters
|
|
290
297
|
if not result.adapters:
|
|
@@ -292,7 +299,7 @@ def interactive(
|
|
|
292
299
|
console.print(
|
|
293
300
|
"[dim]Make sure your .env file contains adapter credentials[/dim]"
|
|
294
301
|
)
|
|
295
|
-
raise typer.Exit(1)
|
|
302
|
+
raise typer.Exit(1) from None
|
|
296
303
|
|
|
297
304
|
console.print("\n[bold]Detected adapter configurations:[/bold]")
|
|
298
305
|
for i, adapter in enumerate(result.adapters, 1):
|
|
@@ -332,7 +339,7 @@ def interactive(
|
|
|
332
339
|
if choice in [1, 2]:
|
|
333
340
|
if not primary:
|
|
334
341
|
console.print("[red]No recommended adapter found[/red]")
|
|
335
|
-
raise typer.Exit(1)
|
|
342
|
+
raise typer.Exit(1) from None
|
|
336
343
|
adapters_to_save = [primary]
|
|
337
344
|
default_adapter = primary.adapter_type
|
|
338
345
|
elif choice == 3:
|
|
@@ -348,7 +355,7 @@ def interactive(
|
|
|
348
355
|
default_adapter = selected.adapter_type
|
|
349
356
|
else:
|
|
350
357
|
console.print("[red]Invalid choice[/red]")
|
|
351
|
-
raise typer.Exit(1)
|
|
358
|
+
raise typer.Exit(1) from None
|
|
352
359
|
else: # choice == 4
|
|
353
360
|
adapters_to_save = result.adapters
|
|
354
361
|
default_adapter = (
|
|
@@ -389,22 +396,281 @@ def interactive(
|
|
|
389
396
|
|
|
390
397
|
console.print(f" ✅ Added {discovered_adapter.adapter_type}")
|
|
391
398
|
|
|
392
|
-
# Save
|
|
399
|
+
# Save (always to project config for security)
|
|
393
400
|
try:
|
|
401
|
+
resolver.save_project_config(config, proj_path)
|
|
402
|
+
config_location = proj_path / resolver.PROJECT_CONFIG_SUBPATH
|
|
403
|
+
|
|
394
404
|
if save_global:
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
resolver.save_project_config(config, proj_path)
|
|
399
|
-
config_location = proj_path / resolver.PROJECT_CONFIG_SUBPATH
|
|
405
|
+
console.print(
|
|
406
|
+
"[yellow]Note: Global config deprecated for security. Saved to project config instead.[/yellow]"
|
|
407
|
+
)
|
|
400
408
|
|
|
401
409
|
console.print(f"\n[green]✅ Configuration saved to:[/green] {config_location}")
|
|
402
410
|
console.print(f"[green]✅ Default adapter:[/green] {config.default_adapter}")
|
|
403
411
|
|
|
404
412
|
except Exception as e:
|
|
405
413
|
console.print(f"\n[red]Failed to save configuration:[/red] {e}")
|
|
414
|
+
raise typer.Exit(1) from None
|
|
415
|
+
|
|
416
|
+
|
|
417
|
+
@app.command(name="1password-status")
|
|
418
|
+
def onepassword_status() -> None:
|
|
419
|
+
"""Check 1Password CLI installation and authentication status."""
|
|
420
|
+
console.print(
|
|
421
|
+
Panel.fit(
|
|
422
|
+
"[bold cyan]1Password CLI Status[/bold cyan]\n"
|
|
423
|
+
"Checking 1Password integration...",
|
|
424
|
+
border_style="cyan",
|
|
425
|
+
)
|
|
426
|
+
)
|
|
427
|
+
|
|
428
|
+
status = check_op_cli_status()
|
|
429
|
+
|
|
430
|
+
# Create status table
|
|
431
|
+
table = Table(title="1Password CLI Status")
|
|
432
|
+
table.add_column("Component", style="cyan")
|
|
433
|
+
table.add_column("Status", style="white")
|
|
434
|
+
|
|
435
|
+
# CLI installed
|
|
436
|
+
if status["installed"]:
|
|
437
|
+
table.add_row(
|
|
438
|
+
"CLI Installed", f"[green]✓ Yes[/green] (version {status['version']})"
|
|
439
|
+
)
|
|
440
|
+
else:
|
|
441
|
+
table.add_row("CLI Installed", "[red]✗ No[/red]")
|
|
442
|
+
console.print(table)
|
|
443
|
+
console.print(
|
|
444
|
+
"\n[yellow]Install 1Password CLI:[/yellow]\n"
|
|
445
|
+
" macOS: brew install 1password-cli\n"
|
|
446
|
+
" Linux: See https://developer.1password.com/docs/cli/get-started/\n"
|
|
447
|
+
" Windows: See https://developer.1password.com/docs/cli/get-started/"
|
|
448
|
+
)
|
|
449
|
+
return
|
|
450
|
+
|
|
451
|
+
# Authentication
|
|
452
|
+
if status["authenticated"]:
|
|
453
|
+
table.add_row("Authentication", "[green]✓ Signed in[/green]")
|
|
454
|
+
|
|
455
|
+
# Show accounts
|
|
456
|
+
if status["accounts"]:
|
|
457
|
+
for account in status["accounts"]:
|
|
458
|
+
account_url = account.get("url", "N/A")
|
|
459
|
+
account_email = account.get("email", "N/A")
|
|
460
|
+
table.add_row(" Account", f"{account_email} ({account_url})")
|
|
461
|
+
else:
|
|
462
|
+
table.add_row("Authentication", "[yellow]⚠ Not signed in[/yellow]")
|
|
463
|
+
|
|
464
|
+
console.print(table)
|
|
465
|
+
|
|
466
|
+
if not status["authenticated"]:
|
|
467
|
+
console.print("\n[yellow]Sign in to 1Password:[/yellow]\n" " Run: op signin\n")
|
|
468
|
+
else:
|
|
469
|
+
console.print(
|
|
470
|
+
"\n[green]✓ 1Password CLI is ready to use![/green]\n\n"
|
|
471
|
+
"You can now use .env files with op:// secret references.\n"
|
|
472
|
+
"Run 'mcp-ticketer discover 1password-template' to create template files."
|
|
473
|
+
)
|
|
474
|
+
|
|
475
|
+
|
|
476
|
+
@app.command(name="1password-template")
|
|
477
|
+
def onepassword_template(
|
|
478
|
+
adapter: str = typer.Argument(
|
|
479
|
+
...,
|
|
480
|
+
help="Adapter type (linear, github, jira, aitrackdown)",
|
|
481
|
+
),
|
|
482
|
+
vault: str = typer.Option(
|
|
483
|
+
"Development",
|
|
484
|
+
"--vault",
|
|
485
|
+
"-v",
|
|
486
|
+
help="1Password vault name for secret references",
|
|
487
|
+
),
|
|
488
|
+
item: str | None = typer.Option(
|
|
489
|
+
None,
|
|
490
|
+
"--item",
|
|
491
|
+
"-i",
|
|
492
|
+
help="1Password item name (defaults to adapter name)",
|
|
493
|
+
),
|
|
494
|
+
output: Path | None = typer.Option(
|
|
495
|
+
None,
|
|
496
|
+
"--output",
|
|
497
|
+
"-o",
|
|
498
|
+
help="Output file path (defaults to .env.1password)",
|
|
499
|
+
),
|
|
500
|
+
) -> None:
|
|
501
|
+
"""Create a .env template file with 1Password secret references.
|
|
502
|
+
|
|
503
|
+
This creates a template file that uses op:// secret references,
|
|
504
|
+
which can be used with: op run --env-file=.env.1password -- <command>
|
|
505
|
+
|
|
506
|
+
Examples:
|
|
507
|
+
# Create Linear template
|
|
508
|
+
mcp-ticketer discover 1password-template linear
|
|
509
|
+
|
|
510
|
+
# Create GitHub template with custom vault
|
|
511
|
+
mcp-ticketer discover 1password-template github --vault=Production
|
|
512
|
+
|
|
513
|
+
# Create template with custom item name
|
|
514
|
+
mcp-ticketer discover 1password-template jira --item="JIRA API Keys"
|
|
515
|
+
|
|
516
|
+
"""
|
|
517
|
+
# Check if op CLI is available
|
|
518
|
+
status = check_op_cli_status()
|
|
519
|
+
if not status["installed"]:
|
|
520
|
+
console.print(
|
|
521
|
+
"[red]1Password CLI not installed.[/red]\n\n"
|
|
522
|
+
"Install it first:\n"
|
|
523
|
+
" macOS: brew install 1password-cli\n"
|
|
524
|
+
" Other: https://developer.1password.com/docs/cli/get-started/"
|
|
525
|
+
)
|
|
406
526
|
raise typer.Exit(1)
|
|
407
527
|
|
|
528
|
+
# Set default output path
|
|
529
|
+
if output is None:
|
|
530
|
+
output = Path(f".env.1password.{adapter.lower()}")
|
|
531
|
+
|
|
532
|
+
# Create loader and generate template
|
|
533
|
+
loader = OnePasswordSecretsLoader(OnePasswordConfig())
|
|
534
|
+
loader.create_template_file(output, adapter, vault, item)
|
|
535
|
+
|
|
536
|
+
console.print(
|
|
537
|
+
Panel.fit(
|
|
538
|
+
f"[bold green]✓ Template created![/bold green]\n\n"
|
|
539
|
+
f"File: {output}\n"
|
|
540
|
+
f"Vault: {vault}\n"
|
|
541
|
+
f"Item: {item or adapter.upper()}\n\n"
|
|
542
|
+
f"[bold]Next steps:[/bold]\n"
|
|
543
|
+
f"1. Create item '{item or adapter.upper()}' in 1Password vault '{vault}'\n"
|
|
544
|
+
f"2. Add the required fields to the item\n"
|
|
545
|
+
f"3. Test with: op run --env-file={output} -- mcp-ticketer discover show\n"
|
|
546
|
+
f"4. Save config: op run --env-file={output} -- mcp-ticketer discover save",
|
|
547
|
+
border_style="green",
|
|
548
|
+
)
|
|
549
|
+
)
|
|
550
|
+
|
|
551
|
+
# Show template contents
|
|
552
|
+
console.print("\n[bold]Template contents:[/bold]\n")
|
|
553
|
+
console.print(Panel(output.read_text(), border_style="dim"))
|
|
554
|
+
|
|
555
|
+
|
|
556
|
+
@app.command(name="1password-test")
|
|
557
|
+
def onepassword_test(
|
|
558
|
+
env_file: Path = typer.Option(
|
|
559
|
+
".env.1password",
|
|
560
|
+
"--file",
|
|
561
|
+
"-f",
|
|
562
|
+
help="Path to .env file with op:// references",
|
|
563
|
+
),
|
|
564
|
+
) -> None:
|
|
565
|
+
"""Test 1Password secret resolution from .env file.
|
|
566
|
+
|
|
567
|
+
This command loads secrets from the specified .env file and
|
|
568
|
+
displays the resolved values (with sensitive data masked).
|
|
569
|
+
|
|
570
|
+
Example:
|
|
571
|
+
mcp-ticketer discover 1password-test --file=.env.1password.linear
|
|
572
|
+
|
|
573
|
+
"""
|
|
574
|
+
# Check if file exists
|
|
575
|
+
if not env_file.exists():
|
|
576
|
+
console.print(f"[red]File not found:[/red] {env_file}")
|
|
577
|
+
raise typer.Exit(1)
|
|
578
|
+
|
|
579
|
+
# Check if op CLI is available and authenticated
|
|
580
|
+
status = check_op_cli_status()
|
|
581
|
+
if not status["installed"]:
|
|
582
|
+
console.print("[red]1Password CLI not installed.[/red]")
|
|
583
|
+
raise typer.Exit(1)
|
|
584
|
+
|
|
585
|
+
if not status["authenticated"]:
|
|
586
|
+
console.print(
|
|
587
|
+
"[red]1Password CLI not authenticated.[/red]\n\n" "Run: op signin"
|
|
588
|
+
)
|
|
589
|
+
raise typer.Exit(1)
|
|
590
|
+
|
|
591
|
+
console.print(
|
|
592
|
+
Panel.fit(
|
|
593
|
+
f"[bold cyan]Testing 1Password Secret Resolution[/bold cyan]\n"
|
|
594
|
+
f"File: {env_file}",
|
|
595
|
+
border_style="cyan",
|
|
596
|
+
)
|
|
597
|
+
)
|
|
598
|
+
|
|
599
|
+
# Load secrets
|
|
600
|
+
loader = OnePasswordSecretsLoader(OnePasswordConfig())
|
|
601
|
+
|
|
602
|
+
try:
|
|
603
|
+
secrets = loader.load_secrets_from_env_file(env_file)
|
|
604
|
+
|
|
605
|
+
# Display resolved secrets
|
|
606
|
+
table = Table(title="Resolved Secrets")
|
|
607
|
+
table.add_column("Variable", style="cyan")
|
|
608
|
+
table.add_column("Value", style="green")
|
|
609
|
+
|
|
610
|
+
for key, value in secrets.items():
|
|
611
|
+
# Mask sensitive values
|
|
612
|
+
display_value = _mask_sensitive(value, key)
|
|
613
|
+
table.add_row(key, display_value)
|
|
614
|
+
|
|
615
|
+
console.print(table)
|
|
616
|
+
|
|
617
|
+
console.print(
|
|
618
|
+
f"\n[green]✓ Successfully resolved {len(secrets)} secrets![/green]"
|
|
619
|
+
)
|
|
620
|
+
|
|
621
|
+
# Test discovery with these secrets
|
|
622
|
+
console.print("\n[bold]Testing configuration discovery...[/bold]")
|
|
623
|
+
EnvDiscovery(enable_1password=False) # Already resolved
|
|
624
|
+
|
|
625
|
+
# Temporarily write resolved secrets to test discovery
|
|
626
|
+
import tempfile
|
|
627
|
+
|
|
628
|
+
with tempfile.NamedTemporaryFile(mode="w", suffix=".env", delete=False) as tmp:
|
|
629
|
+
for key, value in secrets.items():
|
|
630
|
+
tmp.write(f"{key}={value}\n")
|
|
631
|
+
tmp_path = Path(tmp.name)
|
|
632
|
+
|
|
633
|
+
try:
|
|
634
|
+
# Mock the env file loading by directly providing secrets
|
|
635
|
+
from ..core.env_discovery import DiscoveryResult
|
|
636
|
+
|
|
637
|
+
DiscoveryResult()
|
|
638
|
+
|
|
639
|
+
# Try to detect adapters from the resolved secrets
|
|
640
|
+
from ..core.env_discovery import EnvDiscovery as ED
|
|
641
|
+
|
|
642
|
+
ed = ED(enable_1password=False)
|
|
643
|
+
ed.project_path = Path.cwd()
|
|
644
|
+
|
|
645
|
+
# Manually detect from secrets dict
|
|
646
|
+
linear_adapter = ed._detect_linear(secrets, str(env_file))
|
|
647
|
+
if linear_adapter:
|
|
648
|
+
console.print("\n[green]✓ Detected Linear configuration[/green]")
|
|
649
|
+
_display_discovered_adapter(linear_adapter, ed)
|
|
650
|
+
|
|
651
|
+
github_adapter = ed._detect_github(secrets, str(env_file))
|
|
652
|
+
if github_adapter:
|
|
653
|
+
console.print("\n[green]✓ Detected GitHub configuration[/green]")
|
|
654
|
+
_display_discovered_adapter(github_adapter, ed)
|
|
655
|
+
|
|
656
|
+
jira_adapter = ed._detect_jira(secrets, str(env_file))
|
|
657
|
+
if jira_adapter:
|
|
658
|
+
console.print("\n[green]✓ Detected JIRA configuration[/green]")
|
|
659
|
+
_display_discovered_adapter(jira_adapter, ed)
|
|
660
|
+
finally:
|
|
661
|
+
tmp_path.unlink()
|
|
662
|
+
|
|
663
|
+
except Exception as e:
|
|
664
|
+
console.print(f"\n[red]Failed to resolve secrets:[/red] {e}")
|
|
665
|
+
console.print(
|
|
666
|
+
"\n[yellow]Troubleshooting:[/yellow]\n"
|
|
667
|
+
"1. Check that the item exists in 1Password\n"
|
|
668
|
+
"2. Verify the vault name is correct\n"
|
|
669
|
+
"3. Ensure all field names match\n"
|
|
670
|
+
f"4. Run: op inject --in-file={env_file} (to see detailed errors)"
|
|
671
|
+
)
|
|
672
|
+
raise typer.Exit(1) from None
|
|
673
|
+
|
|
408
674
|
|
|
409
675
|
if __name__ == "__main__":
|
|
410
676
|
app()
|
|
@@ -2,11 +2,12 @@
|
|
|
2
2
|
|
|
3
3
|
import json
|
|
4
4
|
from pathlib import Path
|
|
5
|
-
from typing import Literal
|
|
5
|
+
from typing import Literal
|
|
6
6
|
|
|
7
7
|
from rich.console import Console
|
|
8
8
|
|
|
9
|
-
from .mcp_configure import
|
|
9
|
+
from .mcp_configure import load_project_config
|
|
10
|
+
from .python_detection import get_mcp_ticketer_python
|
|
10
11
|
|
|
11
12
|
console = Console()
|
|
12
13
|
|
|
@@ -73,19 +74,21 @@ def save_gemini_config(config_path: Path, config: dict) -> None:
|
|
|
73
74
|
|
|
74
75
|
|
|
75
76
|
def create_gemini_server_config(
|
|
76
|
-
|
|
77
|
+
python_path: str, project_config: dict, project_path: str | None = None
|
|
77
78
|
) -> dict:
|
|
78
79
|
"""Create Gemini MCP server configuration for mcp-ticketer.
|
|
79
80
|
|
|
80
81
|
Args:
|
|
81
|
-
|
|
82
|
+
python_path: Path to Python executable in mcp-ticketer venv
|
|
82
83
|
project_config: Project configuration from .mcp-ticketer/config.json
|
|
83
|
-
|
|
84
|
+
project_path: Project directory path (optional)
|
|
84
85
|
|
|
85
86
|
Returns:
|
|
86
87
|
Gemini MCP server configuration dict
|
|
87
88
|
|
|
88
89
|
"""
|
|
90
|
+
# Use Python module invocation pattern (works regardless of where package is installed)
|
|
91
|
+
|
|
89
92
|
# Get adapter configuration
|
|
90
93
|
adapter = project_config.get("default_adapter", "aitrackdown")
|
|
91
94
|
adapters_config = project_config.get("adapters", {})
|
|
@@ -94,9 +97,9 @@ def create_gemini_server_config(
|
|
|
94
97
|
# Build environment variables
|
|
95
98
|
env_vars = {}
|
|
96
99
|
|
|
97
|
-
# Add PYTHONPATH
|
|
98
|
-
if
|
|
99
|
-
env_vars["PYTHONPATH"] =
|
|
100
|
+
# Add PYTHONPATH for project context
|
|
101
|
+
if project_path:
|
|
102
|
+
env_vars["PYTHONPATH"] = project_path
|
|
100
103
|
|
|
101
104
|
# Add adapter type
|
|
102
105
|
env_vars["MCP_TICKETER_ADAPTER"] = adapter
|
|
@@ -105,9 +108,9 @@ def create_gemini_server_config(
|
|
|
105
108
|
if adapter == "aitrackdown":
|
|
106
109
|
# Set base path for local adapter
|
|
107
110
|
base_path = adapter_config.get("base_path", ".aitrackdown")
|
|
108
|
-
if
|
|
109
|
-
# Use absolute path if
|
|
110
|
-
env_vars["MCP_TICKETER_BASE_PATH"] = str(Path(
|
|
111
|
+
if project_path:
|
|
112
|
+
# Use absolute path if project_path is provided
|
|
113
|
+
env_vars["MCP_TICKETER_BASE_PATH"] = str(Path(project_path) / base_path)
|
|
111
114
|
else:
|
|
112
115
|
env_vars["MCP_TICKETER_BASE_PATH"] = base_path
|
|
113
116
|
|
|
@@ -135,10 +138,15 @@ def create_gemini_server_config(
|
|
|
135
138
|
if "project_key" in adapter_config:
|
|
136
139
|
env_vars["JIRA_PROJECT_KEY"] = adapter_config["project_key"]
|
|
137
140
|
|
|
141
|
+
# Use Python module invocation pattern
|
|
142
|
+
args = ["-m", "mcp_ticketer.mcp.server"]
|
|
143
|
+
if project_path:
|
|
144
|
+
args.append(project_path)
|
|
145
|
+
|
|
138
146
|
# Create server configuration with Gemini-specific options
|
|
139
147
|
config = {
|
|
140
|
-
"command":
|
|
141
|
-
"args":
|
|
148
|
+
"command": python_path,
|
|
149
|
+
"args": args,
|
|
142
150
|
"env": env_vars,
|
|
143
151
|
"timeout": 15000, # 15 seconds timeout
|
|
144
152
|
"trust": False, # Don't trust by default (security)
|
|
@@ -147,6 +155,72 @@ def create_gemini_server_config(
|
|
|
147
155
|
return config
|
|
148
156
|
|
|
149
157
|
|
|
158
|
+
def remove_gemini_mcp(
|
|
159
|
+
scope: Literal["project", "user"] = "project", dry_run: bool = False
|
|
160
|
+
) -> None:
|
|
161
|
+
"""Remove mcp-ticketer from Gemini CLI configuration.
|
|
162
|
+
|
|
163
|
+
Args:
|
|
164
|
+
scope: Configuration scope - "project" or "user"
|
|
165
|
+
dry_run: Show what would be removed without making changes
|
|
166
|
+
|
|
167
|
+
"""
|
|
168
|
+
# Step 1: Find Gemini config location
|
|
169
|
+
config_type = "user-level" if scope == "user" else "project-level"
|
|
170
|
+
console.print(f"[cyan]🔍 Removing {config_type} Gemini CLI configuration...[/cyan]")
|
|
171
|
+
|
|
172
|
+
gemini_config_path = find_gemini_config(scope)
|
|
173
|
+
console.print(f"[dim]Config location: {gemini_config_path}[/dim]")
|
|
174
|
+
|
|
175
|
+
# Step 2: Check if config file exists
|
|
176
|
+
if not gemini_config_path.exists():
|
|
177
|
+
console.print(
|
|
178
|
+
f"[yellow]⚠ No configuration found at {gemini_config_path}[/yellow]"
|
|
179
|
+
)
|
|
180
|
+
console.print("[dim]mcp-ticketer is not configured for Gemini CLI[/dim]")
|
|
181
|
+
return
|
|
182
|
+
|
|
183
|
+
# Step 3: Load existing Gemini configuration
|
|
184
|
+
gemini_config = load_gemini_config(gemini_config_path)
|
|
185
|
+
|
|
186
|
+
# Step 4: Check if mcp-ticketer is configured
|
|
187
|
+
if "mcp-ticketer" not in gemini_config.get("mcpServers", {}):
|
|
188
|
+
console.print("[yellow]⚠ mcp-ticketer is not configured[/yellow]")
|
|
189
|
+
console.print(f"[dim]No mcp-ticketer entry found in {gemini_config_path}[/dim]")
|
|
190
|
+
return
|
|
191
|
+
|
|
192
|
+
# Step 5: Show what would be removed (dry run or actual removal)
|
|
193
|
+
if dry_run:
|
|
194
|
+
console.print("\n[cyan]DRY RUN - Would remove:[/cyan]")
|
|
195
|
+
console.print(" Server name: mcp-ticketer")
|
|
196
|
+
console.print(f" From: {gemini_config_path}")
|
|
197
|
+
console.print(f" Scope: {config_type}")
|
|
198
|
+
return
|
|
199
|
+
|
|
200
|
+
# Step 6: Remove mcp-ticketer from configuration
|
|
201
|
+
del gemini_config["mcpServers"]["mcp-ticketer"]
|
|
202
|
+
|
|
203
|
+
# Step 7: Save updated configuration
|
|
204
|
+
try:
|
|
205
|
+
save_gemini_config(gemini_config_path, gemini_config)
|
|
206
|
+
console.print("\n[green]✓ Successfully removed mcp-ticketer[/green]")
|
|
207
|
+
console.print(f"[dim]Configuration updated: {gemini_config_path}[/dim]")
|
|
208
|
+
|
|
209
|
+
# Next steps
|
|
210
|
+
console.print("\n[bold cyan]Next Steps:[/bold cyan]")
|
|
211
|
+
if scope == "user":
|
|
212
|
+
console.print("1. Gemini CLI global configuration updated")
|
|
213
|
+
console.print("2. mcp-ticketer will no longer be available in any project")
|
|
214
|
+
else:
|
|
215
|
+
console.print("1. Gemini CLI project configuration updated")
|
|
216
|
+
console.print("2. mcp-ticketer will no longer be available in this project")
|
|
217
|
+
console.print("3. Restart Gemini CLI if currently running")
|
|
218
|
+
|
|
219
|
+
except Exception as e:
|
|
220
|
+
console.print(f"\n[red]✗ Failed to update configuration:[/red] {e}")
|
|
221
|
+
raise
|
|
222
|
+
|
|
223
|
+
|
|
150
224
|
def configure_gemini_mcp(
|
|
151
225
|
scope: Literal["project", "user"] = "project", force: bool = False
|
|
152
226
|
) -> None:
|
|
@@ -157,18 +231,22 @@ def configure_gemini_mcp(
|
|
|
157
231
|
force: Overwrite existing configuration
|
|
158
232
|
|
|
159
233
|
Raises:
|
|
160
|
-
FileNotFoundError: If
|
|
234
|
+
FileNotFoundError: If Python executable or project config not found
|
|
161
235
|
ValueError: If configuration is invalid
|
|
162
236
|
|
|
163
237
|
"""
|
|
164
|
-
# Step 1: Find
|
|
165
|
-
console.print("[cyan]🔍 Finding mcp-ticketer
|
|
238
|
+
# Step 1: Find Python executable
|
|
239
|
+
console.print("[cyan]🔍 Finding mcp-ticketer Python executable...[/cyan]")
|
|
166
240
|
try:
|
|
167
|
-
|
|
168
|
-
console.print(f"[green]✓[/green] Found: {
|
|
169
|
-
except
|
|
170
|
-
console.print(f"[red]✗[/red] {e}")
|
|
171
|
-
raise
|
|
241
|
+
python_path = get_mcp_ticketer_python()
|
|
242
|
+
console.print(f"[green]✓[/green] Found: {python_path}")
|
|
243
|
+
except Exception as e:
|
|
244
|
+
console.print(f"[red]✗[/red] Could not find Python executable: {e}")
|
|
245
|
+
raise FileNotFoundError(
|
|
246
|
+
"Could not find mcp-ticketer Python executable. "
|
|
247
|
+
"Please ensure mcp-ticketer is installed.\n"
|
|
248
|
+
"Install with: pip install mcp-ticketer or pipx install mcp-ticketer"
|
|
249
|
+
) from e
|
|
172
250
|
|
|
173
251
|
# Step 2: Load project configuration
|
|
174
252
|
console.print("\n[cyan]📖 Reading project configuration...[/cyan]")
|
|
@@ -200,9 +278,11 @@ def configure_gemini_mcp(
|
|
|
200
278
|
console.print("[yellow]⚠ Overwriting existing configuration[/yellow]")
|
|
201
279
|
|
|
202
280
|
# Step 6: Create mcp-ticketer server config
|
|
203
|
-
|
|
281
|
+
project_path = str(Path.cwd()) if scope == "project" else None
|
|
204
282
|
server_config = create_gemini_server_config(
|
|
205
|
-
|
|
283
|
+
python_path=python_path,
|
|
284
|
+
project_config=project_config,
|
|
285
|
+
project_path=project_path,
|
|
206
286
|
)
|
|
207
287
|
|
|
208
288
|
# Step 7: Update Gemini configuration
|
|
@@ -221,11 +301,12 @@ def configure_gemini_mcp(
|
|
|
221
301
|
console.print("\n[bold]Configuration Details:[/bold]")
|
|
222
302
|
console.print(" Server name: mcp-ticketer")
|
|
223
303
|
console.print(f" Adapter: {adapter}")
|
|
224
|
-
console.print(f"
|
|
304
|
+
console.print(f" Python: {python_path}")
|
|
305
|
+
console.print(" Command: python -m mcp_ticketer.mcp.server")
|
|
225
306
|
console.print(f" Timeout: {server_config['timeout']}ms")
|
|
226
307
|
console.print(f" Trust: {server_config['trust']}")
|
|
227
|
-
if
|
|
228
|
-
console.print(f"
|
|
308
|
+
if project_path:
|
|
309
|
+
console.print(f" Project path: {project_path}")
|
|
229
310
|
if "env" in server_config:
|
|
230
311
|
console.print(
|
|
231
312
|
f" Environment variables: {list(server_config['env'].keys())}"
|