stravinsky 0.2.52__py3-none-any.whl → 0.4.18__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 stravinsky might be problematic. Click here for more details.
- mcp_bridge/__init__.py +1 -1
- mcp_bridge/auth/token_store.py +113 -11
- mcp_bridge/cli/__init__.py +6 -0
- mcp_bridge/cli/install_hooks.py +1265 -0
- mcp_bridge/cli/session_report.py +585 -0
- mcp_bridge/config/MANIFEST_SCHEMA.md +305 -0
- mcp_bridge/config/README.md +276 -0
- mcp_bridge/config/hook_config.py +249 -0
- mcp_bridge/config/hooks_manifest.json +138 -0
- mcp_bridge/config/rate_limits.py +222 -0
- mcp_bridge/config/skills_manifest.json +128 -0
- mcp_bridge/hooks/HOOKS_SETTINGS.json +175 -0
- mcp_bridge/hooks/README.md +215 -0
- mcp_bridge/hooks/__init__.py +119 -60
- mcp_bridge/hooks/edit_recovery.py +42 -37
- mcp_bridge/hooks/git_noninteractive.py +89 -0
- mcp_bridge/hooks/keyword_detector.py +30 -0
- mcp_bridge/hooks/manager.py +8 -0
- mcp_bridge/hooks/notification_hook.py +103 -0
- mcp_bridge/hooks/parallel_execution.py +111 -0
- mcp_bridge/hooks/pre_compact.py +82 -183
- mcp_bridge/hooks/rules_injector.py +507 -0
- mcp_bridge/hooks/session_notifier.py +125 -0
- mcp_bridge/{native_hooks → hooks}/stravinsky_mode.py +51 -16
- mcp_bridge/hooks/subagent_stop.py +98 -0
- mcp_bridge/hooks/task_validator.py +73 -0
- mcp_bridge/hooks/tmux_manager.py +141 -0
- mcp_bridge/hooks/todo_continuation.py +90 -0
- mcp_bridge/hooks/todo_delegation.py +88 -0
- mcp_bridge/hooks/tool_messaging.py +267 -0
- mcp_bridge/hooks/truncator.py +21 -17
- mcp_bridge/notifications.py +151 -0
- mcp_bridge/prompts/multimodal.py +24 -3
- mcp_bridge/server.py +214 -49
- mcp_bridge/server_tools.py +445 -0
- mcp_bridge/tools/__init__.py +22 -18
- mcp_bridge/tools/agent_manager.py +220 -32
- mcp_bridge/tools/code_search.py +97 -11
- mcp_bridge/tools/lsp/__init__.py +7 -0
- mcp_bridge/tools/lsp/manager.py +448 -0
- mcp_bridge/tools/lsp/tools.py +637 -150
- mcp_bridge/tools/model_invoke.py +208 -106
- mcp_bridge/tools/query_classifier.py +323 -0
- mcp_bridge/tools/semantic_search.py +3042 -0
- mcp_bridge/tools/templates.py +32 -18
- mcp_bridge/update_manager.py +589 -0
- mcp_bridge/update_manager_pypi.py +299 -0
- stravinsky-0.4.18.dist-info/METADATA +468 -0
- stravinsky-0.4.18.dist-info/RECORD +88 -0
- stravinsky-0.4.18.dist-info/entry_points.txt +5 -0
- mcp_bridge/native_hooks/edit_recovery.py +0 -46
- mcp_bridge/native_hooks/todo_delegation.py +0 -54
- mcp_bridge/native_hooks/truncator.py +0 -23
- stravinsky-0.2.52.dist-info/METADATA +0 -204
- stravinsky-0.2.52.dist-info/RECORD +0 -63
- stravinsky-0.2.52.dist-info/entry_points.txt +0 -3
- /mcp_bridge/{native_hooks → hooks}/context.py +0 -0
- {stravinsky-0.2.52.dist-info → stravinsky-0.4.18.dist-info}/WHEEL +0 -0
mcp_bridge/__init__.py
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
__version__ = "0.
|
|
1
|
+
__version__ = "0.4.18"
|
mcp_bridge/auth/token_store.py
CHANGED
|
@@ -8,10 +8,12 @@ Stores OAuth tokens securely using the OS keyring:
|
|
|
8
8
|
"""
|
|
9
9
|
|
|
10
10
|
import json
|
|
11
|
+
import os
|
|
11
12
|
import time
|
|
13
|
+
from pathlib import Path
|
|
12
14
|
from typing import TypedDict
|
|
13
15
|
|
|
14
|
-
import
|
|
16
|
+
from cryptography.fernet import Fernet
|
|
15
17
|
|
|
16
18
|
|
|
17
19
|
class TokenData(TypedDict, total=False):
|
|
@@ -26,13 +28,15 @@ class TokenData(TypedDict, total=False):
|
|
|
26
28
|
|
|
27
29
|
class TokenStore:
|
|
28
30
|
"""
|
|
29
|
-
Secure storage for OAuth tokens using system keyring.
|
|
31
|
+
Secure storage for OAuth tokens using system keyring with encrypted file fallback.
|
|
30
32
|
|
|
31
33
|
Each provider (gemini, openai) stores its tokens separately.
|
|
32
34
|
Tokens are serialized as JSON for storage.
|
|
35
|
+
Falls back to encrypted file storage if keyring fails.
|
|
33
36
|
"""
|
|
34
37
|
|
|
35
38
|
SERVICE_NAME = "stravinsky"
|
|
39
|
+
FALLBACK_DIR = Path.home() / ".stravinsky" / "tokens"
|
|
36
40
|
|
|
37
41
|
def __init__(self, service_name: str | None = None):
|
|
38
42
|
"""Initialize the token store.
|
|
@@ -41,6 +45,63 @@ class TokenStore:
|
|
|
41
45
|
service_name: Override the default service name for testing.
|
|
42
46
|
"""
|
|
43
47
|
self.service_name = service_name or self.SERVICE_NAME
|
|
48
|
+
self._init_fallback_storage()
|
|
49
|
+
|
|
50
|
+
def _init_fallback_storage(self) -> None:
|
|
51
|
+
"""Initialize encrypted file storage fallback directory."""
|
|
52
|
+
self.FALLBACK_DIR.mkdir(parents=True, exist_ok=True)
|
|
53
|
+
|
|
54
|
+
def _get_fallback_path(self, provider: str) -> Path:
|
|
55
|
+
"""Get the path for encrypted fallback storage."""
|
|
56
|
+
return self.FALLBACK_DIR / f"{provider}.enc"
|
|
57
|
+
|
|
58
|
+
def _get_or_create_key(self) -> bytes:
|
|
59
|
+
"""Get or create encryption key for fallback storage."""
|
|
60
|
+
key_file = self.FALLBACK_DIR / ".key"
|
|
61
|
+
if key_file.exists():
|
|
62
|
+
return key_file.read_bytes()
|
|
63
|
+
# Create new key and save it
|
|
64
|
+
key = Fernet.generate_key()
|
|
65
|
+
key_file.write_bytes(key)
|
|
66
|
+
key_file.chmod(0o600) # Read/write for owner only
|
|
67
|
+
return key
|
|
68
|
+
|
|
69
|
+
def _save_encrypted(self, provider: str, data: str) -> None:
|
|
70
|
+
"""Save data to encrypted file."""
|
|
71
|
+
try:
|
|
72
|
+
key = self._get_or_create_key()
|
|
73
|
+
cipher = Fernet(key)
|
|
74
|
+
encrypted = cipher.encrypt(data.encode())
|
|
75
|
+
path = self._get_fallback_path(provider)
|
|
76
|
+
path.write_bytes(encrypted)
|
|
77
|
+
path.chmod(0o600)
|
|
78
|
+
except Exception as e:
|
|
79
|
+
raise RuntimeError(f"Failed to save encrypted token: {e}")
|
|
80
|
+
|
|
81
|
+
def _load_encrypted(self, provider: str) -> str | None:
|
|
82
|
+
"""Load data from encrypted file."""
|
|
83
|
+
try:
|
|
84
|
+
path = self._get_fallback_path(provider)
|
|
85
|
+
if not path.exists():
|
|
86
|
+
return None
|
|
87
|
+
key = self._get_or_create_key()
|
|
88
|
+
cipher = Fernet(key)
|
|
89
|
+
encrypted = path.read_bytes()
|
|
90
|
+
decrypted = cipher.decrypt(encrypted)
|
|
91
|
+
return decrypted.decode()
|
|
92
|
+
except Exception:
|
|
93
|
+
return None
|
|
94
|
+
|
|
95
|
+
def _delete_encrypted(self, provider: str) -> bool:
|
|
96
|
+
"""Delete encrypted token file."""
|
|
97
|
+
try:
|
|
98
|
+
path = self._get_fallback_path(provider)
|
|
99
|
+
if path.exists():
|
|
100
|
+
path.unlink()
|
|
101
|
+
return True
|
|
102
|
+
return False
|
|
103
|
+
except Exception:
|
|
104
|
+
return False
|
|
44
105
|
|
|
45
106
|
def _key(self, provider: str) -> str:
|
|
46
107
|
"""Generate the keyring key for a provider."""
|
|
@@ -56,13 +117,24 @@ class TokenStore:
|
|
|
56
117
|
Returns:
|
|
57
118
|
TokenData if found and valid, None otherwise.
|
|
58
119
|
"""
|
|
120
|
+
# Try keyring first (import inside try to catch backend initialization errors)
|
|
59
121
|
try:
|
|
122
|
+
import keyring
|
|
60
123
|
data = keyring.get_password(self.service_name, self._key(provider))
|
|
61
|
-
if
|
|
62
|
-
return
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
124
|
+
if data:
|
|
125
|
+
return json.loads(data)
|
|
126
|
+
except Exception:
|
|
127
|
+
pass # Fall back to encrypted file (catches KeyringError, import errors, etc.)
|
|
128
|
+
|
|
129
|
+
# Fall back to encrypted file storage
|
|
130
|
+
try:
|
|
131
|
+
data = self._load_encrypted(provider)
|
|
132
|
+
if data:
|
|
133
|
+
return json.loads(data)
|
|
134
|
+
except json.JSONDecodeError:
|
|
135
|
+
pass
|
|
136
|
+
|
|
137
|
+
return None
|
|
66
138
|
|
|
67
139
|
def set_token(
|
|
68
140
|
self,
|
|
@@ -77,6 +149,7 @@ class TokenStore:
|
|
|
77
149
|
Store a token for a provider.
|
|
78
150
|
|
|
79
151
|
Can be called with a TokenData dict or individual parameters.
|
|
152
|
+
Falls back to encrypted file storage if keyring fails.
|
|
80
153
|
|
|
81
154
|
Args:
|
|
82
155
|
provider: The provider name (e.g., 'gemini', 'openai')
|
|
@@ -96,7 +169,27 @@ class TokenStore:
|
|
|
96
169
|
if expires_at:
|
|
97
170
|
token_data["expires_at"] = expires_at
|
|
98
171
|
data = json.dumps(token_data)
|
|
99
|
-
|
|
172
|
+
|
|
173
|
+
# Try keyring first, but always also write to encrypted storage
|
|
174
|
+
# Import inside try to catch backend initialization errors (e.g., "No recommended backend")
|
|
175
|
+
keyring_failed = False
|
|
176
|
+
try:
|
|
177
|
+
import keyring
|
|
178
|
+
keyring.set_password(self.service_name, self._key(provider), data)
|
|
179
|
+
except Exception:
|
|
180
|
+
# Keyring failed - fall back to encrypted file
|
|
181
|
+
keyring_failed = True
|
|
182
|
+
|
|
183
|
+
# Always write to encrypted file storage as fallback
|
|
184
|
+
try:
|
|
185
|
+
self._save_encrypted(provider, data)
|
|
186
|
+
except Exception as e:
|
|
187
|
+
# Only fail if both backends failed
|
|
188
|
+
if keyring_failed:
|
|
189
|
+
raise RuntimeError(
|
|
190
|
+
f"Failed to save token to both keyring and encrypted storage: {e}"
|
|
191
|
+
)
|
|
192
|
+
|
|
100
193
|
|
|
101
194
|
def delete_token(self, provider: str) -> bool:
|
|
102
195
|
"""
|
|
@@ -108,11 +201,20 @@ class TokenStore:
|
|
|
108
201
|
Returns:
|
|
109
202
|
True if deleted, False if not found.
|
|
110
203
|
"""
|
|
204
|
+
# Try keyring first (import inside try to catch backend initialization errors)
|
|
205
|
+
deleted_from_keyring = False
|
|
111
206
|
try:
|
|
207
|
+
import keyring
|
|
112
208
|
keyring.delete_password(self.service_name, self._key(provider))
|
|
113
|
-
|
|
114
|
-
except
|
|
115
|
-
|
|
209
|
+
deleted_from_keyring = True
|
|
210
|
+
except Exception:
|
|
211
|
+
# Keyring failed (no backend, delete error, etc.) - continue to encrypted file
|
|
212
|
+
pass
|
|
213
|
+
|
|
214
|
+
# Also delete from encrypted file storage
|
|
215
|
+
deleted_from_file = self._delete_encrypted(provider)
|
|
216
|
+
|
|
217
|
+
return deleted_from_keyring or deleted_from_file
|
|
116
218
|
|
|
117
219
|
def has_valid_token(self, provider: str) -> bool:
|
|
118
220
|
"""
|