stravinsky 0.1.2__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.

@@ -0,0 +1,212 @@
1
+ """
2
+ Skill Loader - Claude Code Slash Command Discovery
3
+
4
+ Discovers and lists available skills (slash commands) from:
5
+ 1. Project-local .claude/commands/
6
+ 2. User-global ~/.claude/commands/
7
+
8
+ Skills are markdown files with frontmatter defining the command behavior.
9
+ """
10
+
11
+ import os
12
+ import re
13
+ from pathlib import Path
14
+ from typing import Any
15
+
16
+
17
+ def parse_frontmatter(content: str) -> tuple[dict[str, Any], str]:
18
+ """
19
+ Parse YAML frontmatter from markdown content.
20
+
21
+ Returns:
22
+ Tuple of (metadata dict, body content)
23
+ """
24
+ if not content.startswith("---"):
25
+ return {}, content
26
+
27
+ # Find the closing ---
28
+ end_match = content.find("---", 3)
29
+ if end_match == -1:
30
+ return {}, content
31
+
32
+ frontmatter = content[3:end_match].strip()
33
+ body = content[end_match + 3:].strip()
34
+
35
+ # Simple YAML parsing for key: value pairs
36
+ metadata = {}
37
+ for line in frontmatter.split("\n"):
38
+ if ":" in line:
39
+ key, _, value = line.partition(":")
40
+ key = key.strip()
41
+ value = value.strip().strip('"').strip("'")
42
+ metadata[key] = value
43
+
44
+ return metadata, body
45
+
46
+
47
+ def discover_skills(project_path: str | None = None) -> list[dict[str, Any]]:
48
+ """
49
+ Discover available skills/commands.
50
+
51
+ Searches:
52
+ 1. Project-local: {project}/.claude/commands/
53
+ 2. User-global: ~/.claude/commands/
54
+
55
+ Args:
56
+ project_path: Project directory to search (defaults to cwd)
57
+
58
+ Returns:
59
+ List of skill definitions.
60
+ """
61
+ skills = []
62
+ search_paths = []
63
+
64
+ # Project-local commands
65
+ project = Path(project_path) if project_path else Path.cwd()
66
+ project_commands = project / ".claude" / "commands"
67
+ if project_commands.exists():
68
+ search_paths.append(("project", project_commands))
69
+
70
+ # User-global commands
71
+ user_commands = Path.home() / ".claude" / "commands"
72
+ if user_commands.exists():
73
+ search_paths.append(("user", user_commands))
74
+
75
+ for scope, commands_dir in search_paths:
76
+ for md_file in commands_dir.glob("*.md"):
77
+ try:
78
+ content = md_file.read_text()
79
+ metadata, body = parse_frontmatter(content)
80
+
81
+ skills.append({
82
+ "name": md_file.stem,
83
+ "scope": scope,
84
+ "path": str(md_file),
85
+ "description": metadata.get("description", ""),
86
+ "allowed_tools": metadata.get("allowed-tools", "").split(",") if metadata.get("allowed-tools") else [],
87
+ "body_preview": body[:200] + "..." if len(body) > 200 else body,
88
+ })
89
+ except Exception:
90
+ continue
91
+
92
+ return skills
93
+
94
+
95
+ def list_skills(project_path: str | None = None) -> str:
96
+ """
97
+ List all available skills for MCP tool.
98
+
99
+ Args:
100
+ project_path: Project directory to search
101
+
102
+ Returns:
103
+ Formatted skill listing.
104
+ """
105
+ skills = discover_skills(project_path)
106
+
107
+ if not skills:
108
+ return "No skills found. Create .claude/commands/*.md files to add skills."
109
+
110
+ lines = [f"Found {len(skills)} skill(s):\n"]
111
+
112
+ for skill in skills:
113
+ scope_badge = "[project]" if skill["scope"] == "project" else "[user]"
114
+ lines.append(f" /{skill['name']} {scope_badge}")
115
+ if skill["description"]:
116
+ lines.append(f" {skill['description']}")
117
+
118
+ return "\n".join(lines)
119
+
120
+
121
+ def get_skill(name: str, project_path: str | None = None) -> str:
122
+ """
123
+ Get the content of a specific skill.
124
+
125
+ Args:
126
+ name: Skill name (filename without .md)
127
+ project_path: Project directory to search
128
+
129
+ Returns:
130
+ Skill content or error message.
131
+ """
132
+ skills = discover_skills(project_path)
133
+
134
+ skill = next((s for s in skills if s["name"] == name), None)
135
+ if not skill:
136
+ available = ", ".join(s["name"] for s in skills)
137
+ return f"Skill '{name}' not found. Available: {available or 'none'}"
138
+
139
+ try:
140
+ content = Path(skill["path"]).read_text()
141
+ metadata, body = parse_frontmatter(content)
142
+
143
+ lines = [
144
+ f"## Skill: {name}",
145
+ f"**Scope**: {skill['scope']}",
146
+ f"**Path**: {skill['path']}",
147
+ ]
148
+
149
+ if metadata.get("description"):
150
+ lines.append(f"**Description**: {metadata['description']}")
151
+
152
+ if metadata.get("allowed-tools"):
153
+ lines.append(f"**Allowed Tools**: {metadata['allowed-tools']}")
154
+
155
+ lines.extend(["", "---", "", body])
156
+
157
+ return "\n".join(lines)
158
+
159
+ except Exception as e:
160
+ return f"Error reading skill: {e}"
161
+
162
+
163
+ def create_skill(
164
+ name: str,
165
+ description: str,
166
+ content: str,
167
+ scope: str = "project",
168
+ project_path: str | None = None,
169
+ ) -> str:
170
+ """
171
+ Create a new skill file.
172
+
173
+ Args:
174
+ name: Skill name (will be used as filename)
175
+ description: Short description for frontmatter
176
+ content: Skill body content
177
+ scope: "project" or "user"
178
+ project_path: Project directory for project-scope skills
179
+
180
+ Returns:
181
+ Success or error message.
182
+ """
183
+ # Sanitize name
184
+ name = re.sub(r"[^a-zA-Z0-9_-]", "-", name.lower())
185
+
186
+ if scope == "project":
187
+ base_dir = Path(project_path) if project_path else Path.cwd()
188
+ commands_dir = base_dir / ".claude" / "commands"
189
+ else:
190
+ commands_dir = Path.home() / ".claude" / "commands"
191
+
192
+ # Ensure directory exists
193
+ commands_dir.mkdir(parents=True, exist_ok=True)
194
+
195
+ skill_path = commands_dir / f"{name}.md"
196
+
197
+ if skill_path.exists():
198
+ return f"Skill '{name}' already exists at {skill_path}"
199
+
200
+ # Create skill content
201
+ skill_content = f"""---
202
+ description: {description}
203
+ ---
204
+
205
+ {content}
206
+ """
207
+
208
+ try:
209
+ skill_path.write_text(skill_content)
210
+ return f"Created skill '{name}' at {skill_path}"
211
+ except Exception as e:
212
+ return f"Error creating skill: {e}"
@@ -0,0 +1 @@
1
+ # Utility functions
@@ -0,0 +1,159 @@
1
+ Metadata-Version: 2.4
2
+ Name: stravinsky
3
+ Version: 0.1.2
4
+ Summary: MCP Bridge for Claude Code with Multi-Model Support. Install globally: claude mcp add --scope user stravinsky -- uvx stravinsky. Add to CLAUDE.md: See https://pypi.org/project/stravinsky/
5
+ Author: Stravinsky Team
6
+ License: MIT
7
+ Keywords: claude,gemini,mcp,oauth,openai
8
+ Requires-Python: >=3.11
9
+ Requires-Dist: aiofiles>=23.1.0
10
+ Requires-Dist: cryptography>=41.0.0
11
+ Requires-Dist: google-auth-oauthlib>=1.0.0
12
+ Requires-Dist: google-auth>=2.20.0
13
+ Requires-Dist: httpx>=0.24.0
14
+ Requires-Dist: jedi>=0.19.2
15
+ Requires-Dist: keyring>=25.7.0
16
+ Requires-Dist: mcp>=1.0.0
17
+ Requires-Dist: openai>=1.0.0
18
+ Requires-Dist: psutil>=5.9.0
19
+ Requires-Dist: pydantic>=2.0.0
20
+ Requires-Dist: python-dotenv>=1.0.0
21
+ Requires-Dist: rich>=13.0.0
22
+ Requires-Dist: ruff>=0.14.10
23
+ Requires-Dist: uuid-utils>=0.9.0
24
+ Provides-Extra: dev
25
+ Requires-Dist: mypy>=1.10.0; extra == 'dev'
26
+ Requires-Dist: pytest-asyncio>=0.23.0; extra == 'dev'
27
+ Requires-Dist: pytest>=8.0.0; extra == 'dev'
28
+ Requires-Dist: ruff>=0.4.0; extra == 'dev'
29
+ Description-Content-Type: text/markdown
30
+
31
+ <div align="center">
32
+ <img src=".gemini/antigravity/brain/d170f128-7b96-4abe-b1dc-77cfecd0fc18/stravinsky_logo_1767303800556.png" width="200" alt="Stravinsky Logo">
33
+ <h1>Stravinsky</h1>
34
+ <p><strong>The Avant-Garde MCP Bridge for Claude Code</strong></p>
35
+ <p><em>Movement • Rhythm • Precision</em></p>
36
+ </div>
37
+
38
+ ---
39
+
40
+ ## What is Stravinsky?
41
+
42
+ **Multi-model AI orchestration** with OAuth authentication for Claude Code.
43
+
44
+ ## Features
45
+
46
+ - 🔐 **OAuth Authentication** - Secure browser-based auth for Google (Gemini) and OpenAI (ChatGPT)
47
+ - 🤖 **Multi-Model Support** - Seamlessly invoke Gemini and GPT models from Claude
48
+ - 🛠️ **31 MCP Tools** - Model invocation, code search, LSP integrations, session management, and more
49
+ - 🧠 **7 Specialized Agents** - Stravinsky (orchestrator), Delphi (advisor), Dewey (documentation), and more
50
+ - 🔄 **Background Tasks** - Spawn parallel agents with full tool access via Claude Code CLI
51
+ - 📝 **LSP Integration** - Full Language Server Protocol support for Python (jedi)
52
+ - 🔍 **AST-Aware Search** - Structural code search and refactoring with ast-grep
53
+
54
+ ## Quick Start
55
+
56
+ ### Installation
57
+
58
+ **From PyPI (Recommended):**
59
+
60
+ ```bash
61
+ # One-shot with uvx - no installation needed!
62
+ claude mcp add stravinsky -- uvx stravinsky
63
+
64
+ # Or install globally first:
65
+ uv tool install stravinsky
66
+ claude mcp add stravinsky -- stravinsky
67
+ ```
68
+
69
+ **From Source (for development):**
70
+
71
+ ```bash
72
+ uv tool install --editable /path/to/stravinsky
73
+ claude mcp add stravinsky -- stravinsky
74
+ ```
75
+
76
+ ### Authentication
77
+
78
+ ```bash
79
+ # Login to Google (Gemini)
80
+ stravinsky-auth login gemini
81
+
82
+ # Login to OpenAI (ChatGPT Plus/Pro required)
83
+ stravinsky-auth login openai
84
+
85
+ # Check status
86
+ stravinsky-auth status
87
+
88
+ # Logout
89
+ stravinsky-auth logout gemini
90
+ ```
91
+
92
+ ## Tools (31)
93
+
94
+ | Category | Tools |
95
+ | ---------------- | ---------------------------------------------------------------------------------- |
96
+ | **Model Invoke** | `invoke_gemini`, `invoke_openai`, `get_system_health` |
97
+ | **Environment** | `get_project_context`, `task_spawn`, `task_status`, `task_list` |
98
+ | **Agents** | `agent_spawn`, `agent_output`, `agent_cancel`, `agent_list`, `agent_progress` |
99
+ | **Code Search** | `ast_grep_search`, `ast_grep_replace`, `grep_search`, `glob_files` |
100
+ | **LSP** | `lsp_diagnostics`, `lsp_hover`, `lsp_goto_definition`, `lsp_find_references`, etc. |
101
+ | **Sessions** | `session_list`, `session_read`, `session_search` |
102
+ | **Skills** | `skill_list`, `skill_get` |
103
+
104
+ ## Agent Prompts (7)
105
+
106
+ | Prompt | Purpose |
107
+ | ----------------- | ------------------------------------------------------------- |
108
+ | `stravinsky` | Task orchestration, planning, and goal-oriented execution. |
109
+ | `delphi` | Strategic technical advisor (GPT-based) for hard debugging. |
110
+ | `dewey` | Documentation and multi-repository research specialist. |
111
+ | `explore` | Specialized for codebase-wide search and structural analysis. |
112
+ | `frontend` | UI/UX Engineer (Gemini-optimized) for component prototyping. |
113
+ | `document_writer` | Technical documentation and specification writer. |
114
+ | `multimodal` | Visual analysis expert for UI screenshots and diagrams. |
115
+
116
+ ## Development
117
+
118
+ ```bash
119
+ # Install in development mode
120
+ uv pip install -e .
121
+
122
+ # Run server
123
+ stravinsky
124
+ ```
125
+
126
+ ## Project Structure
127
+
128
+ ```
129
+ stravinsky/
130
+ ├── mcp_bridge/ # Python MCP server
131
+ │ ├── server.py # Entry point
132
+ │ ├── auth/ # OAuth (Google & OpenAI)
133
+ │ ├── tools/ # Model invoke, search, skills
134
+ │ ├── prompts/ # Agent system prompts
135
+ │ └── config/ # Bridge configuration
136
+ ├── pyproject.toml # Build system
137
+ └── README.md # This file
138
+ ```
139
+
140
+ ## Troubleshooting
141
+
142
+ ### OpenAI "Port 1455 in use"
143
+
144
+ The Codex CLI uses the same port. Stop it with: `killall codex`
145
+
146
+ ### OpenAI Authentication Failed
147
+
148
+ - Ensure you have a ChatGPT Plus/Pro subscription
149
+ - Tokens expire occasionally; run `stravinsky-auth login openai` to refresh
150
+
151
+ ## License
152
+
153
+ MIT
154
+
155
+ ---
156
+
157
+ <div align="center">
158
+ <small>Built with obsession by the Google Deepmind team.</small>
159
+ </div>
@@ -0,0 +1,32 @@
1
+ mcp_bridge/__init__.py,sha256=8hnj-sTcl7-N11RY05wAGqveB_WtfEciYsxjQkXAnmc,156
2
+ mcp_bridge/server.py,sha256=3HbPqRCXN7N94pXh5MzElSN-sU9pHuuf0ntX4r3xOGA,34139
3
+ mcp_bridge/auth/__init__.py,sha256=AGHNtKzqvZYMLQ35Qg6aOabpxBqmkR-pjXv8Iby9oMw,797
4
+ mcp_bridge/auth/cli.py,sha256=z894Jb5buAtzFv8455JuzGeghT-cXphgyp82LryM_NU,6767
5
+ mcp_bridge/auth/oauth.py,sha256=lFTVYN6kjXWAqqYpg9L6XbWYI-Pg-_LFvc4WZAlzM9k,12450
6
+ mcp_bridge/auth/openai_oauth.py,sha256=0Ks2X-NXLCBzqs3xnbj9QLZpugICOX5qB5y5vtDENOo,11522
7
+ mcp_bridge/auth/token_store.py,sha256=3A6TZJ7Wju6QfhALeX4IMhY5jzb9OWMrDzwRbfAukiU,5650
8
+ mcp_bridge/config/__init__.py,sha256=uapHdrSWWrafVKD9CTB1J_7Dw0_RajRhoDGjy9zH21o,256
9
+ mcp_bridge/config/hooks.py,sha256=WvWC6ZUc8y1IXPlGCjLYAAsGGigd5tWeGiw585OGNwA,4624
10
+ mcp_bridge/prompts/__init__.py,sha256=pQmPxbdfS_S-ENB-VX33MXbgfGs8zHfLB_QOBtaPjis,322
11
+ mcp_bridge/prompts/delphi.py,sha256=OgsG5l9lsomj4rpohCtdU28ZW15gugCPG9eYwN340d4,4910
12
+ mcp_bridge/prompts/dewey.py,sha256=5-hRlOIJbfat9G_Lj-N-yhA7xk8c9lSOhK9yhSBqIs0,5261
13
+ mcp_bridge/prompts/document_writer.py,sha256=hiCbxgTU8HKPJkS0eNpPPtzSqDXPreApU2OqiS6zh-0,5618
14
+ mcp_bridge/prompts/explore.py,sha256=121R3bNFbb7AIte69eDtLfxdyHdOo3l16L6cUH1p-kk,3592
15
+ mcp_bridge/prompts/frontend.py,sha256=j91I8k5vcVed13eeX-Ebiv49x9Qj4HO_SQN1xhB8TLQ,4943
16
+ mcp_bridge/prompts/multimodal.py,sha256=Svw11N392LjshalasOd80X0Qw_qtOMqu_lD-_HmQDIo,1936
17
+ mcp_bridge/prompts/stravinsky.py,sha256=aDxuvMhCNCcRhX__LFiHBbvwI11yiL2ZCNcHtXGz27E,12033
18
+ mcp_bridge/tools/__init__.py,sha256=ATTwTUdnzXleHX2FXZpVeGL7x_xNwgCaA7iPFh4EiMs,787
19
+ mcp_bridge/tools/agent_manager.py,sha256=OaRdaJOhkblwuIXTwVfoyboQ8g-8_nI1h5iqnYM6u4A,22274
20
+ mcp_bridge/tools/background_tasks.py,sha256=G1iQ00538q939-DJbzaEsA0uZI2TfwzawFCDx8QC5Hg,5178
21
+ mcp_bridge/tools/code_search.py,sha256=sR-alLQuxaXUFB9hby_wQsQu3Io644wdnpdOM_vm0aw,9978
22
+ mcp_bridge/tools/model_invoke.py,sha256=1oXd8rIQShd5ZLV4OdH8C-cwM42c4dCzBxLvD3rnrro,7059
23
+ mcp_bridge/tools/project_context.py,sha256=bXKxuW1pGjtIbeNjMgpBoQL-d_CI94UPBVpRjUyhX20,4707
24
+ mcp_bridge/tools/session_manager.py,sha256=tCVLLvO-Kttla7OxPImb_NSGL_9aW46ilq5ej_IcnlA,9252
25
+ mcp_bridge/tools/skill_loader.py,sha256=6IhlEEPdNyLjqbtrPMnZdXz7u0KpGpR6EQkXykeC1VQ,6092
26
+ mcp_bridge/tools/lsp/__init__.py,sha256=fLiII9qgeachI3MlkO6uGulfUH3T0YDeyEfO65bbxdw,549
27
+ mcp_bridge/tools/lsp/tools.py,sha256=d0gR8WuOjxLKWIbIXw2n1k6jUOrkoZW7C7TuA08OQwg,16225
28
+ mcp_bridge/utils/__init__.py,sha256=pbHV4nq5SLUYcAyTmLUZYrp293Ctud57X8hwsMGA_BM,20
29
+ stravinsky-0.1.2.dist-info/METADATA,sha256=xBxJn2p3uC7WP21L1EokzZUw7rHfhJglQrjWdlsV0MY,5560
30
+ stravinsky-0.1.2.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
31
+ stravinsky-0.1.2.dist-info/entry_points.txt,sha256=BISwF7i71Oen7jFVmBXz8fxiU11Cp415wPF0xXG2Q3s,97
32
+ stravinsky-0.1.2.dist-info/RECORD,,
@@ -0,0 +1,4 @@
1
+ Wheel-Version: 1.0
2
+ Generator: hatchling 1.28.0
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
@@ -0,0 +1,3 @@
1
+ [console_scripts]
2
+ stravinsky = mcp_bridge.server:main
3
+ stravinsky-auth = mcp_bridge.auth.cli:main