stravinsky 0.2.67__py3-none-any.whl → 0.4.66__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/__init__.py +16 -6
- mcp_bridge/auth/cli.py +202 -11
- mcp_bridge/auth/oauth.py +1 -2
- mcp_bridge/auth/openai_oauth.py +4 -7
- mcp_bridge/auth/token_store.py +112 -11
- mcp_bridge/cli/__init__.py +1 -1
- mcp_bridge/cli/install_hooks.py +503 -107
- mcp_bridge/cli/session_report.py +0 -3
- mcp_bridge/config/MANIFEST_SCHEMA.md +305 -0
- mcp_bridge/config/README.md +276 -0
- mcp_bridge/config/__init__.py +2 -2
- mcp_bridge/config/hook_config.py +247 -0
- mcp_bridge/config/hooks_manifest.json +138 -0
- mcp_bridge/config/rate_limits.py +317 -0
- mcp_bridge/config/skills_manifest.json +128 -0
- mcp_bridge/hooks/HOOKS_SETTINGS.json +17 -4
- mcp_bridge/hooks/__init__.py +19 -4
- mcp_bridge/hooks/agent_reminder.py +4 -4
- mcp_bridge/hooks/auto_slash_command.py +5 -5
- mcp_bridge/hooks/budget_optimizer.py +2 -2
- mcp_bridge/hooks/claude_limits_hook.py +114 -0
- mcp_bridge/hooks/comment_checker.py +3 -4
- mcp_bridge/hooks/compaction.py +2 -2
- mcp_bridge/hooks/context.py +2 -1
- mcp_bridge/hooks/context_monitor.py +2 -2
- mcp_bridge/hooks/delegation_policy.py +85 -0
- mcp_bridge/hooks/directory_context.py +3 -3
- mcp_bridge/hooks/edit_recovery.py +3 -2
- mcp_bridge/hooks/edit_recovery_policy.py +49 -0
- mcp_bridge/hooks/empty_message_sanitizer.py +2 -2
- mcp_bridge/hooks/events.py +160 -0
- mcp_bridge/hooks/git_noninteractive.py +4 -4
- mcp_bridge/hooks/keyword_detector.py +8 -10
- mcp_bridge/hooks/manager.py +43 -22
- mcp_bridge/hooks/notification_hook.py +13 -6
- mcp_bridge/hooks/parallel_enforcement_policy.py +67 -0
- mcp_bridge/hooks/parallel_enforcer.py +5 -5
- mcp_bridge/hooks/parallel_execution.py +22 -10
- mcp_bridge/hooks/post_tool/parallel_validation.py +103 -0
- mcp_bridge/hooks/pre_compact.py +8 -9
- mcp_bridge/hooks/pre_tool/agent_spawn_validator.py +115 -0
- mcp_bridge/hooks/preemptive_compaction.py +2 -3
- mcp_bridge/hooks/routing_notifications.py +80 -0
- mcp_bridge/hooks/rules_injector.py +11 -19
- mcp_bridge/hooks/session_idle.py +4 -4
- mcp_bridge/hooks/session_notifier.py +4 -4
- mcp_bridge/hooks/session_recovery.py +4 -5
- mcp_bridge/hooks/stravinsky_mode.py +1 -1
- mcp_bridge/hooks/subagent_stop.py +1 -3
- mcp_bridge/hooks/task_validator.py +2 -2
- mcp_bridge/hooks/tmux_manager.py +7 -8
- mcp_bridge/hooks/todo_delegation.py +4 -1
- mcp_bridge/hooks/todo_enforcer.py +180 -10
- mcp_bridge/hooks/tool_messaging.py +113 -10
- mcp_bridge/hooks/truncation_policy.py +37 -0
- mcp_bridge/hooks/truncator.py +1 -2
- mcp_bridge/metrics/cost_tracker.py +115 -0
- mcp_bridge/native_search.py +93 -0
- mcp_bridge/native_watcher.py +118 -0
- mcp_bridge/notifications.py +150 -0
- mcp_bridge/orchestrator/enums.py +11 -0
- mcp_bridge/orchestrator/router.py +165 -0
- mcp_bridge/orchestrator/state.py +32 -0
- mcp_bridge/orchestrator/visualization.py +14 -0
- mcp_bridge/orchestrator/wisdom.py +34 -0
- mcp_bridge/prompts/__init__.py +1 -8
- mcp_bridge/prompts/dewey.py +1 -1
- mcp_bridge/prompts/planner.py +2 -4
- mcp_bridge/prompts/stravinsky.py +53 -31
- mcp_bridge/proxy/__init__.py +0 -0
- mcp_bridge/proxy/client.py +70 -0
- mcp_bridge/proxy/model_server.py +157 -0
- mcp_bridge/routing/__init__.py +43 -0
- mcp_bridge/routing/config.py +250 -0
- mcp_bridge/routing/model_tiers.py +135 -0
- mcp_bridge/routing/provider_state.py +261 -0
- mcp_bridge/routing/task_classifier.py +190 -0
- mcp_bridge/server.py +542 -59
- mcp_bridge/server_tools.py +738 -6
- mcp_bridge/tools/__init__.py +40 -25
- mcp_bridge/tools/agent_manager.py +616 -697
- mcp_bridge/tools/background_tasks.py +13 -17
- mcp_bridge/tools/code_search.py +70 -53
- mcp_bridge/tools/continuous_loop.py +0 -1
- mcp_bridge/tools/dashboard.py +19 -0
- mcp_bridge/tools/find_code.py +296 -0
- mcp_bridge/tools/init.py +1 -0
- mcp_bridge/tools/list_directory.py +42 -0
- mcp_bridge/tools/lsp/__init__.py +12 -5
- mcp_bridge/tools/lsp/manager.py +471 -0
- mcp_bridge/tools/lsp/tools.py +723 -207
- mcp_bridge/tools/model_invoke.py +1195 -273
- mcp_bridge/tools/mux_client.py +75 -0
- mcp_bridge/tools/project_context.py +1 -2
- mcp_bridge/tools/query_classifier.py +406 -0
- mcp_bridge/tools/read_file.py +84 -0
- mcp_bridge/tools/replace.py +45 -0
- mcp_bridge/tools/run_shell_command.py +38 -0
- mcp_bridge/tools/search_enhancements.py +347 -0
- mcp_bridge/tools/semantic_search.py +3627 -0
- mcp_bridge/tools/session_manager.py +0 -2
- mcp_bridge/tools/skill_loader.py +0 -1
- mcp_bridge/tools/task_runner.py +5 -7
- mcp_bridge/tools/templates.py +3 -3
- mcp_bridge/tools/tool_search.py +331 -0
- mcp_bridge/tools/write_file.py +29 -0
- mcp_bridge/update_manager.py +585 -0
- mcp_bridge/update_manager_pypi.py +297 -0
- mcp_bridge/utils/cache.py +82 -0
- mcp_bridge/utils/process.py +71 -0
- mcp_bridge/utils/session_state.py +51 -0
- mcp_bridge/utils/truncation.py +76 -0
- stravinsky-0.4.66.dist-info/METADATA +517 -0
- stravinsky-0.4.66.dist-info/RECORD +198 -0
- {stravinsky-0.2.67.dist-info → stravinsky-0.4.66.dist-info}/entry_points.txt +1 -0
- stravinsky_claude_assets/HOOKS_INTEGRATION.md +316 -0
- stravinsky_claude_assets/agents/HOOKS.md +437 -0
- stravinsky_claude_assets/agents/code-reviewer.md +210 -0
- stravinsky_claude_assets/agents/comment_checker.md +580 -0
- stravinsky_claude_assets/agents/debugger.md +254 -0
- stravinsky_claude_assets/agents/delphi.md +495 -0
- stravinsky_claude_assets/agents/dewey.md +248 -0
- stravinsky_claude_assets/agents/explore.md +1198 -0
- stravinsky_claude_assets/agents/frontend.md +472 -0
- stravinsky_claude_assets/agents/implementation-lead.md +164 -0
- stravinsky_claude_assets/agents/momus.md +464 -0
- stravinsky_claude_assets/agents/research-lead.md +141 -0
- stravinsky_claude_assets/agents/stravinsky.md +730 -0
- stravinsky_claude_assets/commands/delphi.md +9 -0
- stravinsky_claude_assets/commands/dewey.md +54 -0
- stravinsky_claude_assets/commands/git-master.md +112 -0
- stravinsky_claude_assets/commands/index.md +49 -0
- stravinsky_claude_assets/commands/publish.md +86 -0
- stravinsky_claude_assets/commands/review.md +73 -0
- stravinsky_claude_assets/commands/str/agent_cancel.md +70 -0
- stravinsky_claude_assets/commands/str/agent_list.md +56 -0
- stravinsky_claude_assets/commands/str/agent_output.md +92 -0
- stravinsky_claude_assets/commands/str/agent_progress.md +74 -0
- stravinsky_claude_assets/commands/str/agent_retry.md +94 -0
- stravinsky_claude_assets/commands/str/cancel.md +51 -0
- stravinsky_claude_assets/commands/str/clean.md +97 -0
- stravinsky_claude_assets/commands/str/continue.md +38 -0
- stravinsky_claude_assets/commands/str/index.md +199 -0
- stravinsky_claude_assets/commands/str/list_watchers.md +96 -0
- stravinsky_claude_assets/commands/str/search.md +205 -0
- stravinsky_claude_assets/commands/str/start_filewatch.md +136 -0
- stravinsky_claude_assets/commands/str/stats.md +71 -0
- stravinsky_claude_assets/commands/str/stop_filewatch.md +89 -0
- stravinsky_claude_assets/commands/str/unwatch.md +42 -0
- stravinsky_claude_assets/commands/str/watch.md +45 -0
- stravinsky_claude_assets/commands/strav.md +53 -0
- stravinsky_claude_assets/commands/stravinsky.md +292 -0
- stravinsky_claude_assets/commands/verify.md +60 -0
- stravinsky_claude_assets/commands/version.md +5 -0
- stravinsky_claude_assets/hooks/README.md +248 -0
- stravinsky_claude_assets/hooks/comment_checker.py +193 -0
- stravinsky_claude_assets/hooks/context.py +38 -0
- stravinsky_claude_assets/hooks/context_monitor.py +153 -0
- stravinsky_claude_assets/hooks/dependency_tracker.py +73 -0
- stravinsky_claude_assets/hooks/edit_recovery.py +46 -0
- stravinsky_claude_assets/hooks/execution_state_tracker.py +68 -0
- stravinsky_claude_assets/hooks/notification_hook.py +103 -0
- stravinsky_claude_assets/hooks/notification_hook_v2.py +96 -0
- stravinsky_claude_assets/hooks/parallel_execution.py +241 -0
- stravinsky_claude_assets/hooks/parallel_reinforcement.py +106 -0
- stravinsky_claude_assets/hooks/parallel_reinforcement_v2.py +112 -0
- stravinsky_claude_assets/hooks/pre_compact.py +123 -0
- stravinsky_claude_assets/hooks/ralph_loop.py +173 -0
- stravinsky_claude_assets/hooks/session_recovery.py +263 -0
- stravinsky_claude_assets/hooks/stop_hook.py +89 -0
- stravinsky_claude_assets/hooks/stravinsky_metrics.py +164 -0
- stravinsky_claude_assets/hooks/stravinsky_mode.py +146 -0
- stravinsky_claude_assets/hooks/subagent_stop.py +98 -0
- stravinsky_claude_assets/hooks/todo_continuation.py +111 -0
- stravinsky_claude_assets/hooks/todo_delegation.py +96 -0
- stravinsky_claude_assets/hooks/tool_messaging.py +281 -0
- stravinsky_claude_assets/hooks/truncator.py +23 -0
- stravinsky_claude_assets/rules/deployment_safety.md +51 -0
- stravinsky_claude_assets/rules/integration_wiring.md +89 -0
- stravinsky_claude_assets/rules/pypi_deployment.md +220 -0
- stravinsky_claude_assets/rules/stravinsky_orchestrator.md +32 -0
- stravinsky_claude_assets/settings.json +152 -0
- stravinsky_claude_assets/skills/chrome-devtools/SKILL.md +81 -0
- stravinsky_claude_assets/skills/sqlite/SKILL.md +77 -0
- stravinsky_claude_assets/skills/supabase/SKILL.md +74 -0
- stravinsky_claude_assets/task_dependencies.json +34 -0
- stravinsky-0.2.67.dist-info/METADATA +0 -284
- stravinsky-0.2.67.dist-info/RECORD +0 -76
- {stravinsky-0.2.67.dist-info → stravinsky-0.4.66.dist-info}/WHEEL +0 -0
mcp_bridge/server.py
CHANGED
|
@@ -7,22 +7,21 @@ Optimized for extremely fast startup and protocol compliance:
|
|
|
7
7
|
- Robust crash logging to stderr and /tmp.
|
|
8
8
|
"""
|
|
9
9
|
|
|
10
|
-
import sys
|
|
11
|
-
import os
|
|
12
10
|
import asyncio
|
|
13
11
|
import logging
|
|
12
|
+
import os
|
|
13
|
+
import sys
|
|
14
14
|
import time
|
|
15
15
|
from typing import Any
|
|
16
16
|
|
|
17
17
|
from mcp.server import Server
|
|
18
18
|
from mcp.server.stdio import stdio_server
|
|
19
19
|
from mcp.types import (
|
|
20
|
-
|
|
21
|
-
TextContent,
|
|
22
|
-
Resource,
|
|
20
|
+
GetPromptResult,
|
|
23
21
|
Prompt,
|
|
24
22
|
PromptMessage,
|
|
25
|
-
|
|
23
|
+
TextContent,
|
|
24
|
+
Tool,
|
|
26
25
|
)
|
|
27
26
|
|
|
28
27
|
from . import __version__
|
|
@@ -35,6 +34,26 @@ logging.basicConfig(
|
|
|
35
34
|
)
|
|
36
35
|
logger = logging.getLogger(__name__)
|
|
37
36
|
|
|
37
|
+
# --- LOAD .env FILES (GEMINI_API_KEY, etc.) ---
|
|
38
|
+
# Load from ~/.stravinsky/.env (dedicated config location)
|
|
39
|
+
try:
|
|
40
|
+
from pathlib import Path
|
|
41
|
+
|
|
42
|
+
from dotenv import load_dotenv
|
|
43
|
+
|
|
44
|
+
# Load from ~/.env (user-global, lowest priority)
|
|
45
|
+
home_env = Path.home() / ".env"
|
|
46
|
+
if home_env.exists():
|
|
47
|
+
load_dotenv(home_env, override=False)
|
|
48
|
+
|
|
49
|
+
# Load from ~/.stravinsky/.env (stravinsky config, takes precedence)
|
|
50
|
+
stravinsky_env = Path.home() / ".stravinsky" / ".env"
|
|
51
|
+
if stravinsky_env.exists():
|
|
52
|
+
load_dotenv(stravinsky_env, override=True)
|
|
53
|
+
logger.info(f"[Config] Loaded environment from {stravinsky_env}")
|
|
54
|
+
except ImportError:
|
|
55
|
+
pass # python-dotenv not installed, skip
|
|
56
|
+
|
|
38
57
|
|
|
39
58
|
# Pre-async crash logger
|
|
40
59
|
def install_emergency_logger():
|
|
@@ -95,56 +114,9 @@ async def list_tools() -> list[Tool]:
|
|
|
95
114
|
return get_tool_definitions()
|
|
96
115
|
|
|
97
116
|
|
|
98
|
-
def _format_tool_log(name: str, arguments: dict[str, Any]) -> str:
|
|
99
|
-
"""Format a concise log message for tool calls."""
|
|
100
|
-
# LSP tools - show file:line
|
|
101
|
-
if name.startswith("lsp_"):
|
|
102
|
-
file_path = arguments.get("file_path", "")
|
|
103
|
-
if file_path:
|
|
104
|
-
# Shorten path to last 2 components
|
|
105
|
-
parts = file_path.split("/")
|
|
106
|
-
short_path = "/".join(parts[-2:]) if len(parts) > 2 else file_path
|
|
107
|
-
line = arguments.get("line", "")
|
|
108
|
-
if line:
|
|
109
|
-
return f"→ {name}: {short_path}:{line}"
|
|
110
|
-
return f"→ {name}: {short_path}"
|
|
111
|
-
query = arguments.get("query", "")
|
|
112
|
-
if query:
|
|
113
|
-
return f"→ {name}: query='{query[:40]}'"
|
|
114
|
-
return f"→ {name}"
|
|
115
|
-
|
|
116
|
-
# Model invocation - show agent context if present
|
|
117
|
-
if name in ("invoke_gemini", "invoke_openai"):
|
|
118
|
-
agent_ctx = arguments.get("agent_context", {})
|
|
119
|
-
agent_type = agent_ctx.get("agent_type", "direct") if agent_ctx else "direct"
|
|
120
|
-
model = arguments.get("model", "default")
|
|
121
|
-
prompt = arguments.get("prompt", "")
|
|
122
|
-
# Summarize prompt
|
|
123
|
-
summary = " ".join(prompt.split())[:80] + "..." if len(prompt) > 80 else prompt
|
|
124
|
-
return f"[{agent_type}] → {model}: {summary}"
|
|
125
|
-
|
|
126
|
-
# Search tools - show pattern
|
|
127
|
-
if name in ("grep_search", "ast_grep_search", "ast_grep_replace"):
|
|
128
|
-
pattern = arguments.get("pattern", "")[:50]
|
|
129
|
-
return f"→ {name}: pattern='{pattern}'"
|
|
130
|
-
|
|
131
|
-
# Agent tools - show agent type/task_id
|
|
132
|
-
if name == "agent_spawn":
|
|
133
|
-
agent_type = arguments.get("agent_type", "explore")
|
|
134
|
-
desc = arguments.get("description", "")[:40]
|
|
135
|
-
return f"→ {name}: [{agent_type}] {desc}"
|
|
136
|
-
if name in ("agent_output", "agent_cancel", "agent_progress"):
|
|
137
|
-
task_id = arguments.get("task_id", "")
|
|
138
|
-
return f"→ {name}: {task_id}"
|
|
139
|
-
|
|
140
|
-
# Default - just tool name
|
|
141
|
-
return f"→ {name}"
|
|
142
|
-
|
|
143
|
-
|
|
144
117
|
@server.call_tool()
|
|
145
118
|
async def call_tool(name: str, arguments: dict[str, Any]) -> list[TextContent]:
|
|
146
119
|
"""Handle tool calls with deep lazy loading of implementations."""
|
|
147
|
-
logger.info(_format_tool_log(name, arguments))
|
|
148
120
|
hook_manager = get_hook_manager_lazy()
|
|
149
121
|
token_store = get_token_store()
|
|
150
122
|
|
|
@@ -167,6 +139,17 @@ async def call_tool(name: str, arguments: dict[str, Any]) -> list[TextContent]:
|
|
|
167
139
|
thinking_budget=arguments.get("thinking_budget", 0),
|
|
168
140
|
)
|
|
169
141
|
|
|
142
|
+
elif name == "invoke_gemini_agentic":
|
|
143
|
+
from .tools.model_invoke import invoke_gemini_agentic
|
|
144
|
+
|
|
145
|
+
result_content = await invoke_gemini_agentic(
|
|
146
|
+
token_store=token_store,
|
|
147
|
+
prompt=arguments["prompt"],
|
|
148
|
+
model=arguments.get("model", "gemini-3-flash"),
|
|
149
|
+
max_turns=arguments.get("max_turns", 10),
|
|
150
|
+
timeout=arguments.get("timeout", 120),
|
|
151
|
+
)
|
|
152
|
+
|
|
170
153
|
elif name == "invoke_openai":
|
|
171
154
|
from .tools.model_invoke import invoke_openai
|
|
172
155
|
|
|
@@ -177,6 +160,7 @@ async def call_tool(name: str, arguments: dict[str, Any]) -> list[TextContent]:
|
|
|
177
160
|
temperature=arguments.get("temperature", 0.7),
|
|
178
161
|
max_tokens=arguments.get("max_tokens", 4096),
|
|
179
162
|
thinking_budget=arguments.get("thinking_budget", 0),
|
|
163
|
+
reasoning_effort=arguments.get("reasoning_effort", "medium"),
|
|
180
164
|
)
|
|
181
165
|
|
|
182
166
|
# --- CONTEXT DISPATCH ---
|
|
@@ -190,6 +174,19 @@ async def call_tool(name: str, arguments: dict[str, Any]) -> list[TextContent]:
|
|
|
190
174
|
|
|
191
175
|
result_content = await get_system_health()
|
|
192
176
|
|
|
177
|
+
elif name == "semantic_health":
|
|
178
|
+
from .tools.semantic_search import semantic_health
|
|
179
|
+
|
|
180
|
+
result_content = await semantic_health(
|
|
181
|
+
project_path=arguments.get("project_path", "."),
|
|
182
|
+
provider=arguments.get("provider", "ollama"),
|
|
183
|
+
)
|
|
184
|
+
|
|
185
|
+
elif name == "lsp_health":
|
|
186
|
+
from .tools.lsp.tools import lsp_health
|
|
187
|
+
|
|
188
|
+
result_content = await lsp_health()
|
|
189
|
+
|
|
193
190
|
# --- SEARCH DISPATCH ---
|
|
194
191
|
elif name == "grep_search":
|
|
195
192
|
from .tools.code_search import grep_search
|
|
@@ -200,6 +197,13 @@ async def call_tool(name: str, arguments: dict[str, Any]) -> list[TextContent]:
|
|
|
200
197
|
file_pattern=arguments.get("file_pattern", ""),
|
|
201
198
|
)
|
|
202
199
|
|
|
200
|
+
elif name == "list_directory":
|
|
201
|
+
from .tools.list_directory import list_directory
|
|
202
|
+
|
|
203
|
+
result_content = await list_directory(
|
|
204
|
+
path=arguments["path"],
|
|
205
|
+
)
|
|
206
|
+
|
|
203
207
|
elif name == "ast_grep_search":
|
|
204
208
|
from .tools.code_search import ast_grep_search
|
|
205
209
|
|
|
@@ -228,6 +232,56 @@ async def call_tool(name: str, arguments: dict[str, Any]) -> list[TextContent]:
|
|
|
228
232
|
directory=arguments.get("directory", "."),
|
|
229
233
|
)
|
|
230
234
|
|
|
235
|
+
elif name == "read_file":
|
|
236
|
+
from .tools.read_file import read_file
|
|
237
|
+
|
|
238
|
+
result_content = await read_file(
|
|
239
|
+
path=arguments["path"],
|
|
240
|
+
offset=arguments.get("offset", 0),
|
|
241
|
+
limit=arguments.get("limit"),
|
|
242
|
+
)
|
|
243
|
+
|
|
244
|
+
elif name == "write_file":
|
|
245
|
+
from .tools.write_file import write_file
|
|
246
|
+
|
|
247
|
+
result_content = await write_file(
|
|
248
|
+
path=arguments["path"],
|
|
249
|
+
content=arguments["content"],
|
|
250
|
+
)
|
|
251
|
+
|
|
252
|
+
elif name == "replace":
|
|
253
|
+
from .tools.replace import replace
|
|
254
|
+
|
|
255
|
+
result_content = await replace(
|
|
256
|
+
path=arguments["path"],
|
|
257
|
+
old_string=arguments["old_string"],
|
|
258
|
+
new_string=arguments["new_string"],
|
|
259
|
+
instruction=arguments["instruction"],
|
|
260
|
+
expected_replacements=arguments.get("expected_replacements", 1),
|
|
261
|
+
)
|
|
262
|
+
|
|
263
|
+
elif name == "run_shell_command":
|
|
264
|
+
from .tools.run_shell_command import run_shell_command
|
|
265
|
+
|
|
266
|
+
result_content = await run_shell_command(
|
|
267
|
+
command=arguments["command"],
|
|
268
|
+
description=arguments["description"],
|
|
269
|
+
dir_path=arguments.get("dir_path", "."),
|
|
270
|
+
)
|
|
271
|
+
|
|
272
|
+
elif name == "tool_search":
|
|
273
|
+
from .tools.tool_search import search_tools
|
|
274
|
+
from .server_tools import get_tool_definitions
|
|
275
|
+
|
|
276
|
+
# Get all registered tool definitions to search through
|
|
277
|
+
all_tools = get_tool_definitions()
|
|
278
|
+
|
|
279
|
+
result_content = search_tools(
|
|
280
|
+
query=arguments["query"],
|
|
281
|
+
tools=all_tools,
|
|
282
|
+
top_k=arguments.get("top_k", 5),
|
|
283
|
+
)
|
|
284
|
+
|
|
231
285
|
# --- SESSION DISPATCH ---
|
|
232
286
|
elif name == "session_list":
|
|
233
287
|
from .tools.session_manager import list_sessions
|
|
@@ -318,7 +372,15 @@ async def call_tool(name: str, arguments: dict[str, Any]) -> list[TextContent]:
|
|
|
318
372
|
elif name == "agent_list":
|
|
319
373
|
from .tools.agent_manager import agent_list
|
|
320
374
|
|
|
321
|
-
result_content = await agent_list()
|
|
375
|
+
result_content = await agent_list(show_all=arguments.get("show_all", True))
|
|
376
|
+
|
|
377
|
+
elif name == "agent_cleanup":
|
|
378
|
+
from .tools.agent_manager import agent_cleanup
|
|
379
|
+
|
|
380
|
+
result_content = await agent_cleanup(
|
|
381
|
+
max_age_minutes=arguments.get("max_age_minutes", 30),
|
|
382
|
+
statuses=arguments.get("statuses"),
|
|
383
|
+
)
|
|
322
384
|
|
|
323
385
|
elif name == "agent_progress":
|
|
324
386
|
from .tools.agent_manager import agent_progress
|
|
@@ -423,6 +485,28 @@ async def call_tool(name: str, arguments: dict[str, Any]) -> list[TextContent]:
|
|
|
423
485
|
character=arguments["character"],
|
|
424
486
|
)
|
|
425
487
|
|
|
488
|
+
elif name == "lsp_code_action_resolve":
|
|
489
|
+
from .tools.lsp import lsp_code_action_resolve
|
|
490
|
+
|
|
491
|
+
result_content = await lsp_code_action_resolve(
|
|
492
|
+
file_path=arguments["file_path"],
|
|
493
|
+
action_code=arguments["action_code"],
|
|
494
|
+
line=arguments.get("line"),
|
|
495
|
+
)
|
|
496
|
+
|
|
497
|
+
elif name == "lsp_extract_refactor":
|
|
498
|
+
from .tools.lsp import lsp_extract_refactor
|
|
499
|
+
|
|
500
|
+
result_content = await lsp_extract_refactor(
|
|
501
|
+
file_path=arguments["file_path"],
|
|
502
|
+
start_line=arguments["start_line"],
|
|
503
|
+
start_char=arguments["start_char"],
|
|
504
|
+
end_line=arguments["end_line"],
|
|
505
|
+
end_char=arguments["end_char"],
|
|
506
|
+
new_name=arguments["new_name"],
|
|
507
|
+
kind=arguments.get("kind", "function"),
|
|
508
|
+
)
|
|
509
|
+
|
|
426
510
|
elif name == "lsp_servers":
|
|
427
511
|
from .tools.lsp import lsp_servers
|
|
428
512
|
|
|
@@ -436,6 +520,222 @@ async def call_tool(name: str, arguments: dict[str, Any]) -> list[TextContent]:
|
|
|
436
520
|
severity=arguments.get("severity", "all"),
|
|
437
521
|
)
|
|
438
522
|
|
|
523
|
+
elif name == "semantic_search":
|
|
524
|
+
from .tools.semantic_search import semantic_search
|
|
525
|
+
|
|
526
|
+
result_content = await semantic_search(
|
|
527
|
+
query=arguments["query"],
|
|
528
|
+
project_path=arguments.get("project_path", "."),
|
|
529
|
+
n_results=arguments.get("n_results", 10),
|
|
530
|
+
language=arguments.get("language"),
|
|
531
|
+
node_type=arguments.get("node_type"),
|
|
532
|
+
provider=arguments.get("provider", "ollama"),
|
|
533
|
+
)
|
|
534
|
+
|
|
535
|
+
elif name == "hybrid_search":
|
|
536
|
+
from .tools.semantic_search import hybrid_search
|
|
537
|
+
|
|
538
|
+
result_content = await hybrid_search(
|
|
539
|
+
query=arguments["query"],
|
|
540
|
+
pattern=arguments.get("pattern"),
|
|
541
|
+
project_path=arguments.get("project_path", "."),
|
|
542
|
+
n_results=arguments.get("n_results", 10),
|
|
543
|
+
language=arguments.get("language"),
|
|
544
|
+
provider=arguments.get("provider", "ollama"),
|
|
545
|
+
)
|
|
546
|
+
|
|
547
|
+
elif name == "find_code":
|
|
548
|
+
from .tools.find_code import find_code
|
|
549
|
+
|
|
550
|
+
result_content = await find_code(
|
|
551
|
+
query=arguments["query"],
|
|
552
|
+
search_type=arguments.get("search_type", "auto"),
|
|
553
|
+
project_path=arguments.get("project_path", "."),
|
|
554
|
+
language=arguments.get("language"),
|
|
555
|
+
n_results=arguments.get("n_results", 10),
|
|
556
|
+
provider=arguments.get("provider", "ollama"),
|
|
557
|
+
)
|
|
558
|
+
|
|
559
|
+
elif name == "multi_query_search":
|
|
560
|
+
from .tools.search_enhancements import multi_query_search
|
|
561
|
+
|
|
562
|
+
result_content = await multi_query_search(
|
|
563
|
+
query=arguments["query"],
|
|
564
|
+
project_path=arguments.get("project_path", "."),
|
|
565
|
+
n_results=arguments.get("n_results", 10),
|
|
566
|
+
num_expansions=arguments.get("num_expansions", 3),
|
|
567
|
+
language=arguments.get("language"),
|
|
568
|
+
node_type=arguments.get("node_type"),
|
|
569
|
+
provider=arguments.get("provider", "ollama"),
|
|
570
|
+
)
|
|
571
|
+
|
|
572
|
+
elif name == "decomposed_search":
|
|
573
|
+
from .tools.search_enhancements import decomposed_search
|
|
574
|
+
|
|
575
|
+
result_content = await decomposed_search(
|
|
576
|
+
query=arguments["query"],
|
|
577
|
+
project_path=arguments.get("project_path", "."),
|
|
578
|
+
n_results=arguments.get("n_results", 10),
|
|
579
|
+
language=arguments.get("language"),
|
|
580
|
+
node_type=arguments.get("node_type"),
|
|
581
|
+
provider=arguments.get("provider", "ollama"),
|
|
582
|
+
)
|
|
583
|
+
|
|
584
|
+
elif name == "enhanced_search":
|
|
585
|
+
from .tools.search_enhancements import enhanced_search
|
|
586
|
+
|
|
587
|
+
result_content = await enhanced_search(
|
|
588
|
+
query=arguments["query"],
|
|
589
|
+
project_path=arguments.get("project_path", "."),
|
|
590
|
+
n_results=arguments.get("n_results", 10),
|
|
591
|
+
mode=arguments.get("mode", "auto"),
|
|
592
|
+
language=arguments.get("language"),
|
|
593
|
+
node_type=arguments.get("node_type"),
|
|
594
|
+
provider=arguments.get("provider", "ollama"),
|
|
595
|
+
)
|
|
596
|
+
|
|
597
|
+
elif name == "get_cost_report":
|
|
598
|
+
from .tools.dashboard import get_cost_report
|
|
599
|
+
|
|
600
|
+
result_content = await get_cost_report(
|
|
601
|
+
session_id=arguments.get("session_id"),
|
|
602
|
+
)
|
|
603
|
+
|
|
604
|
+
elif name == "semantic_index":
|
|
605
|
+
from .tools.semantic_search import index_codebase
|
|
606
|
+
|
|
607
|
+
result_content = await index_codebase(
|
|
608
|
+
project_path=arguments.get("project_path", "."),
|
|
609
|
+
force=arguments.get("force", False),
|
|
610
|
+
provider=arguments.get("provider", "ollama"),
|
|
611
|
+
)
|
|
612
|
+
|
|
613
|
+
elif name == "semantic_stats":
|
|
614
|
+
from .tools.semantic_search import semantic_stats
|
|
615
|
+
|
|
616
|
+
result_content = await semantic_stats(
|
|
617
|
+
project_path=arguments.get("project_path", "."),
|
|
618
|
+
provider=arguments.get("provider", "ollama"),
|
|
619
|
+
)
|
|
620
|
+
|
|
621
|
+
elif name == "start_file_watcher":
|
|
622
|
+
import json
|
|
623
|
+
|
|
624
|
+
from .tools.semantic_search import start_file_watcher
|
|
625
|
+
|
|
626
|
+
try:
|
|
627
|
+
watcher = await start_file_watcher(
|
|
628
|
+
project_path=arguments.get("project_path", "."),
|
|
629
|
+
provider=arguments.get("provider", "ollama"),
|
|
630
|
+
debounce_seconds=arguments.get("debounce_seconds", 2.0),
|
|
631
|
+
)
|
|
632
|
+
|
|
633
|
+
result_content = json.dumps(
|
|
634
|
+
{
|
|
635
|
+
"status": "started",
|
|
636
|
+
"project_path": str(watcher.project_path),
|
|
637
|
+
"debounce_seconds": watcher.debounce_seconds,
|
|
638
|
+
"provider": watcher.store.provider_name,
|
|
639
|
+
"is_running": watcher.is_running(),
|
|
640
|
+
},
|
|
641
|
+
indent=2,
|
|
642
|
+
)
|
|
643
|
+
except ValueError as e:
|
|
644
|
+
# No index exists
|
|
645
|
+
result_content = json.dumps(
|
|
646
|
+
{"error": str(e), "hint": "Run semantic_index() before starting file watcher"},
|
|
647
|
+
indent=2,
|
|
648
|
+
)
|
|
649
|
+
print(f"⚠️ start_file_watcher ValueError: {e}", file=sys.stderr)
|
|
650
|
+
except Exception as e:
|
|
651
|
+
# Unexpected error
|
|
652
|
+
import traceback
|
|
653
|
+
|
|
654
|
+
result_content = json.dumps(
|
|
655
|
+
{
|
|
656
|
+
"error": f"{type(e).__name__}: {str(e)}",
|
|
657
|
+
"hint": "Check MCP server logs for details",
|
|
658
|
+
},
|
|
659
|
+
indent=2,
|
|
660
|
+
)
|
|
661
|
+
print(f"❌ start_file_watcher error: {e}", file=sys.stderr)
|
|
662
|
+
traceback.print_exc(file=sys.stderr)
|
|
663
|
+
|
|
664
|
+
elif name == "stop_file_watcher":
|
|
665
|
+
import json
|
|
666
|
+
|
|
667
|
+
from .tools.semantic_search import stop_file_watcher
|
|
668
|
+
|
|
669
|
+
stopped = stop_file_watcher(
|
|
670
|
+
project_path=arguments.get("project_path", "."),
|
|
671
|
+
)
|
|
672
|
+
|
|
673
|
+
result_content = json.dumps(
|
|
674
|
+
{"stopped": stopped, "project_path": arguments.get("project_path", ".")}, indent=2
|
|
675
|
+
)
|
|
676
|
+
|
|
677
|
+
elif name == "cancel_indexing":
|
|
678
|
+
from .tools.semantic_search import cancel_indexing
|
|
679
|
+
|
|
680
|
+
result_content = cancel_indexing(
|
|
681
|
+
project_path=arguments.get("project_path", "."),
|
|
682
|
+
provider=arguments.get("provider", "ollama"),
|
|
683
|
+
)
|
|
684
|
+
|
|
685
|
+
elif name == "delete_index":
|
|
686
|
+
from .tools.semantic_search import delete_index
|
|
687
|
+
|
|
688
|
+
result_content = delete_index(
|
|
689
|
+
project_path=arguments.get("project_path", "."),
|
|
690
|
+
provider=arguments.get("provider"), # None if not specified
|
|
691
|
+
delete_all=arguments.get("delete_all", False),
|
|
692
|
+
)
|
|
693
|
+
|
|
694
|
+
elif name == "list_file_watchers":
|
|
695
|
+
import json
|
|
696
|
+
|
|
697
|
+
from .tools.semantic_search import list_file_watchers
|
|
698
|
+
|
|
699
|
+
result_content = json.dumps(list_file_watchers(), indent=2)
|
|
700
|
+
|
|
701
|
+
elif name == "multi_query_search":
|
|
702
|
+
from .tools.semantic_search import multi_query_search
|
|
703
|
+
|
|
704
|
+
result_content = await multi_query_search(
|
|
705
|
+
query=arguments["query"],
|
|
706
|
+
project_path=arguments.get("project_path", "."),
|
|
707
|
+
n_results=arguments.get("n_results", 10),
|
|
708
|
+
num_expansions=arguments.get("num_expansions", 3),
|
|
709
|
+
language=arguments.get("language"),
|
|
710
|
+
node_type=arguments.get("node_type"),
|
|
711
|
+
provider=arguments.get("provider", "ollama"),
|
|
712
|
+
)
|
|
713
|
+
|
|
714
|
+
elif name == "decomposed_search":
|
|
715
|
+
from .tools.semantic_search import decomposed_search
|
|
716
|
+
|
|
717
|
+
result_content = await decomposed_search(
|
|
718
|
+
query=arguments["query"],
|
|
719
|
+
project_path=arguments.get("project_path", "."),
|
|
720
|
+
n_results=arguments.get("n_results", 10),
|
|
721
|
+
language=arguments.get("language"),
|
|
722
|
+
node_type=arguments.get("node_type"),
|
|
723
|
+
provider=arguments.get("provider", "ollama"),
|
|
724
|
+
)
|
|
725
|
+
|
|
726
|
+
elif name == "enhanced_search":
|
|
727
|
+
from .tools.semantic_search import enhanced_search
|
|
728
|
+
|
|
729
|
+
result_content = await enhanced_search(
|
|
730
|
+
query=arguments["query"],
|
|
731
|
+
project_path=arguments.get("project_path", "."),
|
|
732
|
+
n_results=arguments.get("n_results", 10),
|
|
733
|
+
mode=arguments.get("mode", "auto"),
|
|
734
|
+
language=arguments.get("language"),
|
|
735
|
+
node_type=arguments.get("node_type"),
|
|
736
|
+
provider=arguments.get("provider", "ollama"),
|
|
737
|
+
)
|
|
738
|
+
|
|
439
739
|
else:
|
|
440
740
|
result_content = f"Unknown tool: {name}"
|
|
441
741
|
|
|
@@ -482,7 +782,7 @@ async def list_prompts() -> list[Prompt]:
|
|
|
482
782
|
@server.get_prompt()
|
|
483
783
|
async def get_prompt(name: str, arguments: dict[str, str] | None) -> GetPromptResult:
|
|
484
784
|
"""Get a specific prompt content (lazy loaded)."""
|
|
485
|
-
from .prompts import
|
|
785
|
+
from .prompts import delphi, dewey, document_writer, explore, frontend, multimodal, stravinsky
|
|
486
786
|
|
|
487
787
|
prompts_map = {
|
|
488
788
|
"stravinsky": ("Stravinsky orchestrator system prompt", stravinsky.get_stravinsky_prompt),
|
|
@@ -511,8 +811,105 @@ async def get_prompt(name: str, arguments: dict[str, str] | None) -> GetPromptRe
|
|
|
511
811
|
)
|
|
512
812
|
|
|
513
813
|
|
|
814
|
+
def sync_user_assets():
|
|
815
|
+
"""
|
|
816
|
+
Copy package assets to user scope (~/.claude/) on every MCP load.
|
|
817
|
+
|
|
818
|
+
This ensures all repos get the latest commands, hooks, rules, and agents
|
|
819
|
+
from the installed Stravinsky package.
|
|
820
|
+
|
|
821
|
+
Handles both:
|
|
822
|
+
- Development: .claude/ relative to project root
|
|
823
|
+
- Installed package: stravinsky_claude_assets/ in site-packages
|
|
824
|
+
"""
|
|
825
|
+
from pathlib import Path
|
|
826
|
+
import shutil
|
|
827
|
+
|
|
828
|
+
# Try multiple locations for package assets
|
|
829
|
+
package_dir = Path(__file__).parent.parent # stravinsky/
|
|
830
|
+
|
|
831
|
+
# Location 1: Development - .claude/ at project root
|
|
832
|
+
dev_claude = package_dir / ".claude"
|
|
833
|
+
|
|
834
|
+
# Location 2: Installed package - stravinsky_claude_assets in site-packages
|
|
835
|
+
# When installed via pip/uvx, hatch includes .claude as stravinsky_claude_assets
|
|
836
|
+
installed_claude = package_dir / "stravinsky_claude_assets"
|
|
837
|
+
|
|
838
|
+
# Also check relative to mcp_bridge (alternate install layout)
|
|
839
|
+
mcp_bridge_dir = Path(__file__).parent
|
|
840
|
+
installed_claude_alt = mcp_bridge_dir.parent / "stravinsky_claude_assets"
|
|
841
|
+
|
|
842
|
+
# Find the first existing assets directory
|
|
843
|
+
package_claude = None
|
|
844
|
+
for candidate in [dev_claude, installed_claude, installed_claude_alt]:
|
|
845
|
+
if candidate.exists():
|
|
846
|
+
package_claude = candidate
|
|
847
|
+
break
|
|
848
|
+
|
|
849
|
+
# User scope directory
|
|
850
|
+
user_claude = Path.home() / ".claude"
|
|
851
|
+
|
|
852
|
+
if package_claude is None:
|
|
853
|
+
# Try importlib.resources as last resort (Python 3.9+)
|
|
854
|
+
try:
|
|
855
|
+
import importlib.resources as resources
|
|
856
|
+
|
|
857
|
+
# Check if stravinsky_claude_assets is a package
|
|
858
|
+
with resources.files("stravinsky_claude_assets") as assets_path:
|
|
859
|
+
if assets_path.is_dir():
|
|
860
|
+
package_claude = Path(assets_path)
|
|
861
|
+
except (ImportError, ModuleNotFoundError, TypeError):
|
|
862
|
+
pass
|
|
863
|
+
|
|
864
|
+
if package_claude is None:
|
|
865
|
+
logger.debug(f"Package assets not found (checked: {dev_claude}, {installed_claude})")
|
|
866
|
+
return
|
|
867
|
+
|
|
868
|
+
# Directories to sync
|
|
869
|
+
dirs_to_sync = ["commands", "hooks", "rules", "agents"]
|
|
870
|
+
|
|
871
|
+
for dir_name in dirs_to_sync:
|
|
872
|
+
src_dir = package_claude / dir_name
|
|
873
|
+
dst_dir = user_claude / dir_name
|
|
874
|
+
|
|
875
|
+
if not src_dir.exists():
|
|
876
|
+
continue
|
|
877
|
+
|
|
878
|
+
# Create destination if it doesn't exist
|
|
879
|
+
dst_dir.mkdir(parents=True, exist_ok=True)
|
|
880
|
+
|
|
881
|
+
# Copy all files recursively (overwrite if source is newer)
|
|
882
|
+
for src_file in src_dir.rglob("*"):
|
|
883
|
+
if src_file.is_file():
|
|
884
|
+
# Compute relative path
|
|
885
|
+
rel_path = src_file.relative_to(src_dir)
|
|
886
|
+
dst_file = dst_dir / rel_path
|
|
887
|
+
|
|
888
|
+
# Create parent directories
|
|
889
|
+
dst_file.parent.mkdir(parents=True, exist_ok=True)
|
|
890
|
+
|
|
891
|
+
# Copy if source is newer or dest doesn't exist
|
|
892
|
+
should_copy = not dst_file.exists()
|
|
893
|
+
if dst_file.exists():
|
|
894
|
+
src_mtime = src_file.stat().st_mtime
|
|
895
|
+
dst_mtime = dst_file.stat().st_mtime
|
|
896
|
+
should_copy = src_mtime > dst_mtime
|
|
897
|
+
|
|
898
|
+
if should_copy:
|
|
899
|
+
shutil.copy2(src_file, dst_file)
|
|
900
|
+
logger.debug(f"Synced {dir_name}/{rel_path} to user scope")
|
|
901
|
+
|
|
902
|
+
logger.info("Synced package assets to user scope (~/.claude/)")
|
|
903
|
+
|
|
904
|
+
|
|
514
905
|
async def async_main():
|
|
515
906
|
"""Server execution entry point."""
|
|
907
|
+
# Sync package assets to user scope on every MCP load
|
|
908
|
+
try:
|
|
909
|
+
sync_user_assets()
|
|
910
|
+
except Exception as e:
|
|
911
|
+
logger.warning(f"Failed to sync user assets: {e}")
|
|
912
|
+
|
|
516
913
|
# Initialize hooks at runtime, not import time
|
|
517
914
|
try:
|
|
518
915
|
from .hooks import initialize_hooks
|
|
@@ -521,6 +918,16 @@ async def async_main():
|
|
|
521
918
|
except Exception as e:
|
|
522
919
|
logger.error(f"Failed to initialize hooks: {e}")
|
|
523
920
|
|
|
921
|
+
# Clean up stale ChromaDB locks on startup
|
|
922
|
+
try:
|
|
923
|
+
from .tools.semantic_search import cleanup_stale_chromadb_locks
|
|
924
|
+
|
|
925
|
+
removed_count = cleanup_stale_chromadb_locks()
|
|
926
|
+
if removed_count > 0:
|
|
927
|
+
logger.info(f"Cleaned up {removed_count} stale ChromaDB lock(s)")
|
|
928
|
+
except Exception as e:
|
|
929
|
+
logger.warning(f"Failed to cleanup ChromaDB locks: {e}")
|
|
930
|
+
|
|
524
931
|
# Start background token refresh scheduler
|
|
525
932
|
try:
|
|
526
933
|
from .auth.token_refresh import background_token_refresh
|
|
@@ -537,17 +944,23 @@ async def async_main():
|
|
|
537
944
|
write_stream,
|
|
538
945
|
server.create_initialization_options(),
|
|
539
946
|
)
|
|
540
|
-
except Exception
|
|
947
|
+
except Exception:
|
|
541
948
|
logger.critical("Server process crashed in async_main", exc_info=True)
|
|
542
949
|
sys.exit(1)
|
|
950
|
+
finally:
|
|
951
|
+
logger.info("Initiating shutdown sequence...")
|
|
952
|
+
from .tools.lsp.manager import get_lsp_manager
|
|
953
|
+
|
|
954
|
+
lsp_manager = get_lsp_manager()
|
|
955
|
+
await lsp_manager.shutdown()
|
|
543
956
|
|
|
544
957
|
|
|
545
958
|
def main():
|
|
546
959
|
"""Synchronous entry point with CLI arg handling."""
|
|
547
960
|
import argparse
|
|
548
|
-
|
|
549
|
-
from .tools.agent_manager import get_manager
|
|
961
|
+
|
|
550
962
|
from .auth.token_store import TokenStore
|
|
963
|
+
from .tools.agent_manager import get_manager
|
|
551
964
|
|
|
552
965
|
parser = argparse.ArgumentParser(
|
|
553
966
|
description="Stravinsky MCP Bridge - Multi-model AI orchestration for Claude Code. "
|
|
@@ -597,6 +1010,31 @@ def main():
|
|
|
597
1010
|
help="Also clear agent history from .stravinsky/agents.json",
|
|
598
1011
|
)
|
|
599
1012
|
|
|
1013
|
+
# proxy command (model server proxy)
|
|
1014
|
+
proxy_parser = subparsers.add_parser(
|
|
1015
|
+
"proxy",
|
|
1016
|
+
help="Manage the model proxy server",
|
|
1017
|
+
description="Starts or manages the FastAPI model proxy for async execution.",
|
|
1018
|
+
)
|
|
1019
|
+
proxy_subparsers = proxy_parser.add_subparsers(
|
|
1020
|
+
dest="proxy_command", help="Proxy subcommands", metavar="SUBCOMMAND"
|
|
1021
|
+
)
|
|
1022
|
+
proxy_subparsers.add_parser(
|
|
1023
|
+
"start",
|
|
1024
|
+
help="Start the model proxy server",
|
|
1025
|
+
description="Launches the FastAPI server on port 8765.",
|
|
1026
|
+
)
|
|
1027
|
+
proxy_subparsers.add_parser(
|
|
1028
|
+
"stop",
|
|
1029
|
+
help="Stop the model proxy server",
|
|
1030
|
+
description="Terminates any running model proxy process.",
|
|
1031
|
+
)
|
|
1032
|
+
proxy_subparsers.add_parser(
|
|
1033
|
+
"status",
|
|
1034
|
+
help="Check proxy server status",
|
|
1035
|
+
description="Verifies if the proxy server is reachable.",
|
|
1036
|
+
)
|
|
1037
|
+
|
|
600
1038
|
# auth command (authentication)
|
|
601
1039
|
auth_parser = subparsers.add_parser(
|
|
602
1040
|
"auth",
|
|
@@ -708,6 +1146,51 @@ def main():
|
|
|
708
1146
|
print(f"Stopped {count} running agent(s).")
|
|
709
1147
|
return 0
|
|
710
1148
|
|
|
1149
|
+
elif args.command == "proxy":
|
|
1150
|
+
proxy_cmd = getattr(args, "proxy_command", None)
|
|
1151
|
+
import os
|
|
1152
|
+
import sys
|
|
1153
|
+
|
|
1154
|
+
if proxy_cmd == "start":
|
|
1155
|
+
from .proxy.model_server import main as proxy_main
|
|
1156
|
+
|
|
1157
|
+
proxy_main()
|
|
1158
|
+
return 0
|
|
1159
|
+
elif proxy_cmd == "stop":
|
|
1160
|
+
# Simple kill-by-port for now
|
|
1161
|
+
import os
|
|
1162
|
+
import subprocess
|
|
1163
|
+
|
|
1164
|
+
port = int(os.getenv("STRAVINSKY_PROXY_PORT", 8765))
|
|
1165
|
+
try:
|
|
1166
|
+
if sys.platform == "win32":
|
|
1167
|
+
subprocess.run(
|
|
1168
|
+
f"taskkill /F /FI \"PID eq $(netstat -ano | findstr :{port} | awk '{{print $5}}')\"",
|
|
1169
|
+
shell=True,
|
|
1170
|
+
)
|
|
1171
|
+
else:
|
|
1172
|
+
subprocess.run(f"lsof -ti:{port} | xargs kill -9", shell=True)
|
|
1173
|
+
print(f"Stopped proxy on port {port}")
|
|
1174
|
+
except Exception as e:
|
|
1175
|
+
print(f"Failed to stop proxy: {e}")
|
|
1176
|
+
return 0
|
|
1177
|
+
elif proxy_cmd == "status":
|
|
1178
|
+
import socket
|
|
1179
|
+
|
|
1180
|
+
port = int(os.getenv("STRAVINSKY_PROXY_PORT", 8765))
|
|
1181
|
+
host = os.getenv("STRAVINSKY_PROXY_HOST", "127.0.0.1")
|
|
1182
|
+
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
|
|
1183
|
+
s.settimeout(1)
|
|
1184
|
+
try:
|
|
1185
|
+
s.connect((host, port))
|
|
1186
|
+
print(f"✅ Proxy is RUNNING on {host}:{port}")
|
|
1187
|
+
except Exception:
|
|
1188
|
+
print(f"❌ Proxy is NOT RUNNING on {host}:{port}")
|
|
1189
|
+
return 0
|
|
1190
|
+
else:
|
|
1191
|
+
proxy_parser.print_help()
|
|
1192
|
+
return 0
|
|
1193
|
+
|
|
711
1194
|
elif args.command == "auth":
|
|
712
1195
|
auth_cmd = getattr(args, "auth_command", None)
|
|
713
1196
|
token_store = get_token_store()
|
|
@@ -752,4 +1235,4 @@ def main():
|
|
|
752
1235
|
|
|
753
1236
|
|
|
754
1237
|
if __name__ == "__main__":
|
|
755
|
-
main()
|
|
1238
|
+
sys.exit(main())
|