mcp-ticketer 0.2.0__py3-none-any.whl → 2.2.9__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.
- mcp_ticketer/__init__.py +10 -10
- mcp_ticketer/__version__.py +3 -3
- mcp_ticketer/_version_scm.py +1 -0
- mcp_ticketer/adapters/__init__.py +2 -0
- mcp_ticketer/adapters/aitrackdown.py +930 -52
- mcp_ticketer/adapters/asana/__init__.py +15 -0
- mcp_ticketer/adapters/asana/adapter.py +1537 -0
- mcp_ticketer/adapters/asana/client.py +292 -0
- mcp_ticketer/adapters/asana/mappers.py +348 -0
- mcp_ticketer/adapters/asana/types.py +146 -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 +58 -16
- 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/__init__.py +1 -1
- mcp_ticketer/adapters/linear/adapter.py +3810 -462
- mcp_ticketer/adapters/linear/client.py +312 -69
- mcp_ticketer/adapters/linear/mappers.py +305 -85
- mcp_ticketer/adapters/linear/queries.py +317 -17
- mcp_ticketer/adapters/linear/types.py +187 -64
- mcp_ticketer/adapters/linear.py +2 -2
- 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/cache/memory.py +9 -8
- mcp_ticketer/cli/adapter_diagnostics.py +421 -0
- mcp_ticketer/cli/auggie_configure.py +116 -15
- mcp_ticketer/cli/codex_configure.py +274 -82
- mcp_ticketer/cli/configure.py +1323 -151
- mcp_ticketer/cli/cursor_configure.py +314 -0
- mcp_ticketer/cli/diagnostics.py +209 -114
- mcp_ticketer/cli/discover.py +297 -26
- mcp_ticketer/cli/gemini_configure.py +119 -26
- mcp_ticketer/cli/init_command.py +880 -0
- mcp_ticketer/cli/install_mcp_server.py +418 -0
- mcp_ticketer/cli/instruction_commands.py +435 -0
- mcp_ticketer/cli/linear_commands.py +256 -130
- mcp_ticketer/cli/main.py +140 -1284
- mcp_ticketer/cli/mcp_configure.py +1013 -100
- mcp_ticketer/cli/mcp_server_commands.py +415 -0
- mcp_ticketer/cli/migrate_config.py +12 -8
- mcp_ticketer/cli/platform_commands.py +123 -0
- mcp_ticketer/cli/platform_detection.py +477 -0
- mcp_ticketer/cli/platform_installer.py +545 -0
- mcp_ticketer/cli/project_update_commands.py +350 -0
- mcp_ticketer/cli/python_detection.py +126 -0
- mcp_ticketer/cli/queue_commands.py +15 -15
- mcp_ticketer/cli/setup_command.py +794 -0
- mcp_ticketer/cli/simple_health.py +84 -59
- mcp_ticketer/cli/ticket_commands.py +1375 -0
- mcp_ticketer/cli/update_checker.py +313 -0
- mcp_ticketer/cli/utils.py +195 -72
- mcp_ticketer/core/__init__.py +64 -1
- mcp_ticketer/core/adapter.py +618 -18
- mcp_ticketer/core/config.py +77 -68
- mcp_ticketer/core/env_discovery.py +75 -16
- mcp_ticketer/core/env_loader.py +121 -97
- mcp_ticketer/core/exceptions.py +32 -24
- mcp_ticketer/core/http_client.py +26 -26
- mcp_ticketer/core/instructions.py +405 -0
- mcp_ticketer/core/label_manager.py +732 -0
- mcp_ticketer/core/mappers.py +42 -30
- mcp_ticketer/core/milestone_manager.py +252 -0
- mcp_ticketer/core/models.py +566 -19
- mcp_ticketer/core/onepassword_secrets.py +379 -0
- mcp_ticketer/core/priority_matcher.py +463 -0
- mcp_ticketer/core/project_config.py +189 -49
- mcp_ticketer/core/project_utils.py +281 -0
- mcp_ticketer/core/project_validator.py +376 -0
- mcp_ticketer/core/registry.py +3 -3
- mcp_ticketer/core/session_state.py +176 -0
- mcp_ticketer/core/state_matcher.py +592 -0
- mcp_ticketer/core/url_parser.py +425 -0
- mcp_ticketer/core/validators.py +69 -0
- 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/server/constants.py +58 -0
- mcp_ticketer/mcp/server/diagnostic_helper.py +175 -0
- mcp_ticketer/mcp/server/dto.py +195 -0
- mcp_ticketer/mcp/server/main.py +1343 -0
- mcp_ticketer/mcp/server/response_builder.py +206 -0
- mcp_ticketer/mcp/server/routing.py +723 -0
- mcp_ticketer/mcp/server/server_sdk.py +151 -0
- mcp_ticketer/mcp/server/tools/__init__.py +69 -0
- mcp_ticketer/mcp/server/tools/analysis_tools.py +854 -0
- mcp_ticketer/mcp/server/tools/attachment_tools.py +224 -0
- mcp_ticketer/mcp/server/tools/bulk_tools.py +330 -0
- mcp_ticketer/mcp/server/tools/comment_tools.py +152 -0
- mcp_ticketer/mcp/server/tools/config_tools.py +1564 -0
- mcp_ticketer/mcp/server/tools/diagnostic_tools.py +211 -0
- mcp_ticketer/mcp/server/tools/hierarchy_tools.py +942 -0
- mcp_ticketer/mcp/server/tools/instruction_tools.py +295 -0
- 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 +150 -0
- 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 +318 -0
- mcp_ticketer/mcp/server/tools/session_tools.py +308 -0
- mcp_ticketer/mcp/server/tools/ticket_tools.py +1413 -0
- mcp_ticketer/mcp/server/tools/user_ticket_tools.py +364 -0
- mcp_ticketer/queue/__init__.py +1 -0
- mcp_ticketer/queue/health_monitor.py +168 -136
- mcp_ticketer/queue/manager.py +78 -63
- mcp_ticketer/queue/queue.py +108 -21
- mcp_ticketer/queue/run_worker.py +2 -2
- mcp_ticketer/queue/ticket_registry.py +213 -155
- mcp_ticketer/queue/worker.py +96 -58
- mcp_ticketer/utils/__init__.py +5 -0
- mcp_ticketer/utils/token_utils.py +246 -0
- mcp_ticketer-2.2.9.dist-info/METADATA +1396 -0
- mcp_ticketer-2.2.9.dist-info/RECORD +158 -0
- mcp_ticketer-2.2.9.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 -1354
- mcp_ticketer/adapters/jira.py +0 -1011
- mcp_ticketer/mcp/server.py +0 -1895
- mcp_ticketer-0.2.0.dist-info/METADATA +0 -414
- mcp_ticketer-0.2.0.dist-info/RECORD +0 -58
- mcp_ticketer-0.2.0.dist-info/top_level.txt +0 -1
- {mcp_ticketer-0.2.0.dist-info → mcp_ticketer-2.2.9.dist-info}/WHEEL +0 -0
- {mcp_ticketer-0.2.0.dist-info → mcp_ticketer-2.2.9.dist-info}/entry_points.txt +0 -0
- {mcp_ticketer-0.2.0.dist-info → mcp_ticketer-2.2.9.dist-info}/licenses/LICENSE +0 -0
|
@@ -0,0 +1,379 @@
|
|
|
1
|
+
"""1Password CLI integration for secure secret management.
|
|
2
|
+
|
|
3
|
+
This module provides automatic secret loading from 1Password using the op CLI,
|
|
4
|
+
supporting:
|
|
5
|
+
- Detection of op:// secret references in .env files
|
|
6
|
+
- Automatic resolution using `op run` or `op inject`
|
|
7
|
+
- Fallback to regular .env values if 1Password CLI is not available
|
|
8
|
+
- Support for .env.1password template files
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
import logging
|
|
12
|
+
import shutil
|
|
13
|
+
import subprocess
|
|
14
|
+
from dataclasses import dataclass
|
|
15
|
+
from pathlib import Path
|
|
16
|
+
from typing import Any
|
|
17
|
+
|
|
18
|
+
logger = logging.getLogger(__name__)
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
@dataclass
|
|
22
|
+
class OnePasswordConfig:
|
|
23
|
+
"""Configuration for 1Password integration."""
|
|
24
|
+
|
|
25
|
+
enabled: bool = True
|
|
26
|
+
vault: str | None = None # Default vault for secret references
|
|
27
|
+
service_account_token: str | None = None # For CI/CD environments
|
|
28
|
+
fallback_to_env: bool = True # Fall back to regular .env if op CLI unavailable
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
class OnePasswordSecretsLoader:
|
|
32
|
+
"""Load secrets from 1Password using the op CLI.
|
|
33
|
+
|
|
34
|
+
This class provides methods to:
|
|
35
|
+
1. Check if 1Password CLI is installed and authenticated
|
|
36
|
+
2. Resolve op:// secret references in .env files
|
|
37
|
+
3. Load secrets into environment variables
|
|
38
|
+
4. Create .env templates with op:// references
|
|
39
|
+
"""
|
|
40
|
+
|
|
41
|
+
def __init__(self, config: OnePasswordConfig | None = None) -> None:
|
|
42
|
+
"""Initialize the 1Password secrets loader.
|
|
43
|
+
|
|
44
|
+
Args:
|
|
45
|
+
config: Configuration for 1Password integration
|
|
46
|
+
|
|
47
|
+
"""
|
|
48
|
+
self.config = config or OnePasswordConfig()
|
|
49
|
+
self._op_available: bool | None = None
|
|
50
|
+
self._op_authenticated: bool | None = None
|
|
51
|
+
|
|
52
|
+
def is_op_available(self) -> bool:
|
|
53
|
+
"""Check if 1Password CLI is installed.
|
|
54
|
+
|
|
55
|
+
Returns:
|
|
56
|
+
True if op CLI is available, False otherwise
|
|
57
|
+
|
|
58
|
+
"""
|
|
59
|
+
if self._op_available is None:
|
|
60
|
+
self._op_available = shutil.which("op") is not None
|
|
61
|
+
if not self._op_available:
|
|
62
|
+
logger.debug("1Password CLI (op) not found in PATH")
|
|
63
|
+
return self._op_available
|
|
64
|
+
|
|
65
|
+
def is_authenticated(self) -> bool:
|
|
66
|
+
"""Check if user is authenticated with 1Password.
|
|
67
|
+
|
|
68
|
+
Returns:
|
|
69
|
+
True if authenticated, False otherwise
|
|
70
|
+
|
|
71
|
+
"""
|
|
72
|
+
if not self.is_op_available():
|
|
73
|
+
return False
|
|
74
|
+
|
|
75
|
+
if self._op_authenticated is None:
|
|
76
|
+
try:
|
|
77
|
+
# Try to list accounts to check authentication
|
|
78
|
+
result = subprocess.run(
|
|
79
|
+
["op", "account", "list"],
|
|
80
|
+
capture_output=True,
|
|
81
|
+
text=True,
|
|
82
|
+
timeout=5,
|
|
83
|
+
check=False,
|
|
84
|
+
)
|
|
85
|
+
self._op_authenticated = result.returncode == 0
|
|
86
|
+
if not self._op_authenticated:
|
|
87
|
+
logger.debug(
|
|
88
|
+
"1Password CLI not authenticated. Run 'op signin' first."
|
|
89
|
+
)
|
|
90
|
+
except (subprocess.TimeoutExpired, FileNotFoundError) as e:
|
|
91
|
+
logger.debug(f"Error checking 1Password authentication: {e}")
|
|
92
|
+
self._op_authenticated = False
|
|
93
|
+
|
|
94
|
+
return self._op_authenticated
|
|
95
|
+
|
|
96
|
+
def load_secrets_from_env_file(
|
|
97
|
+
self, env_file: Path, output_dict: dict[str, str] | None = None
|
|
98
|
+
) -> dict[str, str]:
|
|
99
|
+
"""Load secrets from .env file, resolving 1Password references.
|
|
100
|
+
|
|
101
|
+
This method:
|
|
102
|
+
1. Checks if the .env file contains op:// references
|
|
103
|
+
2. If yes and op CLI is available, uses op inject to resolve them
|
|
104
|
+
3. If no op references or CLI unavailable, returns regular dotenv values
|
|
105
|
+
|
|
106
|
+
Args:
|
|
107
|
+
env_file: Path to .env file (may contain op:// references)
|
|
108
|
+
output_dict: Optional dict to update with loaded secrets
|
|
109
|
+
|
|
110
|
+
Returns:
|
|
111
|
+
Dictionary of environment variables with secrets resolved
|
|
112
|
+
|
|
113
|
+
"""
|
|
114
|
+
if not env_file.exists():
|
|
115
|
+
logger.warning(f"Environment file not found: {env_file}")
|
|
116
|
+
return output_dict or {}
|
|
117
|
+
|
|
118
|
+
# Read the file to check for op:// references
|
|
119
|
+
content = env_file.read_text(encoding="utf-8")
|
|
120
|
+
has_op_references = "op://" in content
|
|
121
|
+
|
|
122
|
+
if has_op_references and self.is_authenticated():
|
|
123
|
+
# Use op inject to resolve references
|
|
124
|
+
return self._inject_secrets(env_file, output_dict)
|
|
125
|
+
else:
|
|
126
|
+
# Fall back to regular dotenv parsing
|
|
127
|
+
if has_op_references and not self.is_authenticated():
|
|
128
|
+
logger.warning(
|
|
129
|
+
f"File {env_file} contains 1Password references but op CLI "
|
|
130
|
+
"is not authenticated. Using fallback values."
|
|
131
|
+
)
|
|
132
|
+
return self._load_regular_env(env_file, output_dict)
|
|
133
|
+
|
|
134
|
+
def _inject_secrets(
|
|
135
|
+
self, env_file: Path, output_dict: dict[str, str] | None = None
|
|
136
|
+
) -> dict[str, str]:
|
|
137
|
+
"""Use op inject to resolve secret references in .env file.
|
|
138
|
+
|
|
139
|
+
Args:
|
|
140
|
+
env_file: Path to .env file with op:// references
|
|
141
|
+
output_dict: Optional dict to update
|
|
142
|
+
|
|
143
|
+
Returns:
|
|
144
|
+
Dictionary with resolved secrets
|
|
145
|
+
|
|
146
|
+
"""
|
|
147
|
+
try:
|
|
148
|
+
# Use op inject to resolve references
|
|
149
|
+
cmd = ["op", "inject", "--in-file", str(env_file)]
|
|
150
|
+
|
|
151
|
+
# Add service account token if provided
|
|
152
|
+
env = None
|
|
153
|
+
if self.config.service_account_token:
|
|
154
|
+
env = {"OP_SERVICE_ACCOUNT_TOKEN": self.config.service_account_token}
|
|
155
|
+
|
|
156
|
+
result = subprocess.run(
|
|
157
|
+
cmd,
|
|
158
|
+
capture_output=True,
|
|
159
|
+
text=True,
|
|
160
|
+
timeout=30,
|
|
161
|
+
check=True,
|
|
162
|
+
env=env,
|
|
163
|
+
)
|
|
164
|
+
|
|
165
|
+
# Parse the injected output
|
|
166
|
+
secrets = self._parse_env_output(result.stdout)
|
|
167
|
+
|
|
168
|
+
if output_dict is not None:
|
|
169
|
+
output_dict.update(secrets)
|
|
170
|
+
return output_dict
|
|
171
|
+
return secrets
|
|
172
|
+
|
|
173
|
+
except subprocess.CalledProcessError as e:
|
|
174
|
+
logger.error(f"Error injecting 1Password secrets: {e.stderr}")
|
|
175
|
+
if self.config.fallback_to_env:
|
|
176
|
+
logger.info("Falling back to regular .env parsing")
|
|
177
|
+
return self._load_regular_env(env_file, output_dict)
|
|
178
|
+
raise
|
|
179
|
+
except subprocess.TimeoutExpired:
|
|
180
|
+
logger.error("Timeout while injecting 1Password secrets")
|
|
181
|
+
if self.config.fallback_to_env:
|
|
182
|
+
return self._load_regular_env(env_file, output_dict)
|
|
183
|
+
raise
|
|
184
|
+
|
|
185
|
+
def _load_regular_env(
|
|
186
|
+
self, env_file: Path, output_dict: dict[str, str] | None = None
|
|
187
|
+
) -> dict[str, str]:
|
|
188
|
+
"""Load environment variables without 1Password resolution.
|
|
189
|
+
|
|
190
|
+
Args:
|
|
191
|
+
env_file: Path to .env file
|
|
192
|
+
output_dict: Optional dict to update
|
|
193
|
+
|
|
194
|
+
Returns:
|
|
195
|
+
Dictionary of environment variables
|
|
196
|
+
|
|
197
|
+
"""
|
|
198
|
+
from dotenv import dotenv_values
|
|
199
|
+
|
|
200
|
+
values = dotenv_values(env_file)
|
|
201
|
+
|
|
202
|
+
if output_dict is not None:
|
|
203
|
+
output_dict.update(values)
|
|
204
|
+
return output_dict
|
|
205
|
+
return dict(values)
|
|
206
|
+
|
|
207
|
+
def _parse_env_output(self, output: str) -> dict[str, str]:
|
|
208
|
+
"""Parse environment variable output from op inject.
|
|
209
|
+
|
|
210
|
+
Args:
|
|
211
|
+
output: String output from op inject
|
|
212
|
+
|
|
213
|
+
Returns:
|
|
214
|
+
Dictionary of parsed environment variables
|
|
215
|
+
|
|
216
|
+
"""
|
|
217
|
+
env_vars = {}
|
|
218
|
+
for line in output.splitlines():
|
|
219
|
+
line = line.strip()
|
|
220
|
+
if not line or line.startswith("#"):
|
|
221
|
+
continue
|
|
222
|
+
|
|
223
|
+
# Split on first = only
|
|
224
|
+
if "=" in line:
|
|
225
|
+
key, value = line.split("=", 1)
|
|
226
|
+
key = key.strip()
|
|
227
|
+
value = value.strip()
|
|
228
|
+
|
|
229
|
+
# Remove quotes if present
|
|
230
|
+
if value.startswith('"') and value.endswith('"'):
|
|
231
|
+
value = value[1:-1]
|
|
232
|
+
elif value.startswith("'") and value.endswith("'"):
|
|
233
|
+
value = value[1:-1]
|
|
234
|
+
|
|
235
|
+
env_vars[key] = value
|
|
236
|
+
|
|
237
|
+
return env_vars
|
|
238
|
+
|
|
239
|
+
def create_template_file(
|
|
240
|
+
self,
|
|
241
|
+
output_path: Path,
|
|
242
|
+
adapter_type: str,
|
|
243
|
+
vault_name: str = "Development",
|
|
244
|
+
item_name: str | None = None,
|
|
245
|
+
) -> None:
|
|
246
|
+
"""Create a .env template file with 1Password secret references.
|
|
247
|
+
|
|
248
|
+
Args:
|
|
249
|
+
output_path: Path where to create the template file
|
|
250
|
+
adapter_type: Type of adapter (linear, github, jira, aitrackdown)
|
|
251
|
+
vault_name: Name of 1Password vault to use
|
|
252
|
+
item_name: Name of 1Password item (defaults to adapter name)
|
|
253
|
+
|
|
254
|
+
"""
|
|
255
|
+
if item_name is None:
|
|
256
|
+
item_name = adapter_type.upper()
|
|
257
|
+
|
|
258
|
+
templates = {
|
|
259
|
+
"linear": f"""# Linear Configuration with 1Password
|
|
260
|
+
# This file contains secret references that will be resolved by 1Password CLI
|
|
261
|
+
# Run: op run --env-file=.env.1password -- mcp-ticketer discover
|
|
262
|
+
|
|
263
|
+
LINEAR_API_KEY="op://{vault_name}/{item_name}/api_key"
|
|
264
|
+
LINEAR_TEAM_ID="op://{vault_name}/{item_name}/team_id"
|
|
265
|
+
LINEAR_TEAM_KEY="op://{vault_name}/{item_name}/team_key"
|
|
266
|
+
LINEAR_PROJECT_ID="op://{vault_name}/{item_name}/project_id"
|
|
267
|
+
""",
|
|
268
|
+
"github": f"""# GitHub Configuration with 1Password
|
|
269
|
+
# This file contains secret references that will be resolved by 1Password CLI
|
|
270
|
+
# Run: op run --env-file=.env.1password -- mcp-ticketer discover
|
|
271
|
+
|
|
272
|
+
GITHUB_TOKEN="op://{vault_name}/{item_name}/token"
|
|
273
|
+
GITHUB_OWNER="op://{vault_name}/{item_name}/owner"
|
|
274
|
+
GITHUB_REPO="op://{vault_name}/{item_name}/repo"
|
|
275
|
+
""",
|
|
276
|
+
"jira": f"""# JIRA Configuration with 1Password
|
|
277
|
+
# This file contains secret references that will be resolved by 1Password CLI
|
|
278
|
+
# Run: op run --env-file=.env.1password -- mcp-ticketer discover
|
|
279
|
+
|
|
280
|
+
JIRA_SERVER="op://{vault_name}/{item_name}/server"
|
|
281
|
+
JIRA_EMAIL="op://{vault_name}/{item_name}/email"
|
|
282
|
+
JIRA_API_TOKEN="op://{vault_name}/{item_name}/api_token"
|
|
283
|
+
JIRA_PROJECT_KEY="op://{vault_name}/{item_name}/project_key"
|
|
284
|
+
""",
|
|
285
|
+
"aitrackdown": """# AITrackdown Configuration
|
|
286
|
+
# AITrackdown doesn't use API keys, but you can store the base path
|
|
287
|
+
|
|
288
|
+
AITRACKDOWN_PATH=".aitrackdown"
|
|
289
|
+
""",
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
template = templates.get(adapter_type.lower(), "")
|
|
293
|
+
if template:
|
|
294
|
+
output_path.write_text(template, encoding="utf-8")
|
|
295
|
+
logger.info(f"Created 1Password template file: {output_path}")
|
|
296
|
+
else:
|
|
297
|
+
logger.error(f"Unknown adapter type: {adapter_type}")
|
|
298
|
+
|
|
299
|
+
def run_with_secrets(
|
|
300
|
+
self, command: list[str], env_file: Path | None = None
|
|
301
|
+
) -> subprocess.CompletedProcess[str]:
|
|
302
|
+
"""Run a command with secrets loaded from 1Password.
|
|
303
|
+
|
|
304
|
+
Args:
|
|
305
|
+
command: Command and arguments to run
|
|
306
|
+
env_file: Optional .env file with secret references
|
|
307
|
+
|
|
308
|
+
Returns:
|
|
309
|
+
CompletedProcess result
|
|
310
|
+
|
|
311
|
+
"""
|
|
312
|
+
if not self.is_authenticated():
|
|
313
|
+
raise RuntimeError(
|
|
314
|
+
"1Password CLI not authenticated. Run 'op signin' first."
|
|
315
|
+
)
|
|
316
|
+
|
|
317
|
+
cmd = ["op", "run"]
|
|
318
|
+
|
|
319
|
+
if env_file:
|
|
320
|
+
cmd.extend(["--env-file", str(env_file)])
|
|
321
|
+
|
|
322
|
+
cmd.append("--")
|
|
323
|
+
cmd.extend(command)
|
|
324
|
+
|
|
325
|
+
return subprocess.run(cmd, capture_output=True, text=True, check=True)
|
|
326
|
+
|
|
327
|
+
|
|
328
|
+
def check_op_cli_status() -> dict[str, Any]:
|
|
329
|
+
"""Check the status of 1Password CLI installation and authentication.
|
|
330
|
+
|
|
331
|
+
Returns:
|
|
332
|
+
Dictionary with status information
|
|
333
|
+
|
|
334
|
+
"""
|
|
335
|
+
loader = OnePasswordSecretsLoader()
|
|
336
|
+
|
|
337
|
+
status: dict[str, Any] = {
|
|
338
|
+
"installed": loader.is_op_available(),
|
|
339
|
+
"authenticated": False,
|
|
340
|
+
"version": None,
|
|
341
|
+
"accounts": [],
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
if status["installed"]:
|
|
345
|
+
# Get version
|
|
346
|
+
try:
|
|
347
|
+
result = subprocess.run(
|
|
348
|
+
["op", "--version"],
|
|
349
|
+
capture_output=True,
|
|
350
|
+
text=True,
|
|
351
|
+
timeout=5,
|
|
352
|
+
check=False,
|
|
353
|
+
)
|
|
354
|
+
if result.returncode == 0:
|
|
355
|
+
status["version"] = result.stdout.strip()
|
|
356
|
+
except (subprocess.TimeoutExpired, FileNotFoundError):
|
|
357
|
+
pass
|
|
358
|
+
|
|
359
|
+
# Check authentication
|
|
360
|
+
status["authenticated"] = loader.is_authenticated()
|
|
361
|
+
|
|
362
|
+
# Get accounts if authenticated
|
|
363
|
+
if status["authenticated"]:
|
|
364
|
+
try:
|
|
365
|
+
result = subprocess.run(
|
|
366
|
+
["op", "account", "list", "--format=json"],
|
|
367
|
+
capture_output=True,
|
|
368
|
+
text=True,
|
|
369
|
+
timeout=5,
|
|
370
|
+
check=False,
|
|
371
|
+
)
|
|
372
|
+
if result.returncode == 0:
|
|
373
|
+
import json
|
|
374
|
+
|
|
375
|
+
status["accounts"] = json.loads(result.stdout)
|
|
376
|
+
except (subprocess.TimeoutExpired, FileNotFoundError, json.JSONDecodeError):
|
|
377
|
+
pass
|
|
378
|
+
|
|
379
|
+
return status
|