claudemol 0.2.0__tar.gz → 0.4.0__tar.gz
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.
- {claudemol-0.2.0 → claudemol-0.4.0}/PKG-INFO +64 -13
- {claudemol-0.2.0 → claudemol-0.4.0}/README.md +63 -12
- {claudemol-0.2.0 → claudemol-0.4.0}/pyproject.toml +1 -1
- {claudemol-0.2.0 → claudemol-0.4.0}/src/claudemol/__init__.py +10 -4
- claudemol-0.4.0/src/claudemol/cli.py +304 -0
- {claudemol-0.2.0 → claudemol-0.4.0}/src/claudemol/connection.py +28 -0
- claudemol-0.2.0/src/claudemol/cli.py +0 -167
- {claudemol-0.2.0 → claudemol-0.4.0}/.gitignore +0 -0
- {claudemol-0.2.0 → claudemol-0.4.0}/LICENSE +0 -0
- {claudemol-0.2.0 → claudemol-0.4.0}/claude-plugin/README.md +0 -0
- {claudemol-0.2.0 → claudemol-0.4.0}/src/claudemol/plugin.py +0 -0
- {claudemol-0.2.0 → claudemol-0.4.0}/src/claudemol/session.py +0 -0
- {claudemol-0.2.0 → claudemol-0.4.0}/src/claudemol/view.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: claudemol
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.4.0
|
|
4
4
|
Summary: PyMOL integration for Claude Code - control molecular visualization via natural language
|
|
5
5
|
Project-URL: Homepage, https://github.com/ANaka/claudemol
|
|
6
6
|
Project-URL: Repository, https://github.com/ANaka/claudemol
|
|
@@ -30,6 +30,7 @@ Control PyMOL through natural language using Claude Code. This integration enabl
|
|
|
30
30
|
- **Direct socket communication**: Claude Code talks directly to PyMOL (no intermediary server)
|
|
31
31
|
- **Full PyMOL access**: Manipulate representations, colors, views, perform measurements, alignments, and more
|
|
32
32
|
- **Skill-based workflows**: Built-in skills for common tasks like binding site visualization and publication figures
|
|
33
|
+
- **Connect to anything**: Because Claude is the bridge, it can pull in data from online databases (UniProt, PDB, OPM), literature, protein language model annotations, or local analysis scripts and map them directly onto your structure
|
|
33
34
|
|
|
34
35
|
## Architecture
|
|
35
36
|
|
|
@@ -45,22 +46,47 @@ Claude Code → TCP Socket (port 9880) → PyMOL Plugin → cmd.* execution
|
|
|
45
46
|
- [Claude Code](https://docs.anthropic.com/en/docs/claude-code) CLI installed
|
|
46
47
|
- Python 3.10+
|
|
47
48
|
|
|
48
|
-
###
|
|
49
|
+
### 1. Install claudemol
|
|
49
50
|
|
|
50
51
|
```bash
|
|
51
52
|
pip install claudemol
|
|
52
53
|
claudemol setup
|
|
53
54
|
```
|
|
54
55
|
|
|
55
|
-
This
|
|
56
|
+
This configures PyMOL to auto-load the socket plugin and saves your Python path to `~/.claudemol/config.json` so future Claude Code sessions can find it automatically.
|
|
56
57
|
|
|
57
|
-
###
|
|
58
|
+
### 2. Install the Claude Code plugin
|
|
59
|
+
|
|
60
|
+
```
|
|
61
|
+
/plugin marketplace add ANaka/claudemol?path=claude-plugin
|
|
62
|
+
/plugin install claudemol-skills
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
This gives Claude the skills and hooks to work with PyMOL.
|
|
66
|
+
|
|
67
|
+
### 3. Start using it
|
|
58
68
|
|
|
59
69
|
Open Claude Code and say:
|
|
60
70
|
|
|
61
71
|
> "Open PyMOL and load structure 1UBQ"
|
|
62
72
|
|
|
63
|
-
Claude will launch PyMOL
|
|
73
|
+
Claude will launch PyMOL, connect via socket, and load the structure.
|
|
74
|
+
|
|
75
|
+
### Optional: Seamless permissions
|
|
76
|
+
|
|
77
|
+
By default, Claude asks for approval before running each command. To auto-approve PyMOL-related commands, add to your project's `.claude/settings.json`:
|
|
78
|
+
|
|
79
|
+
```json
|
|
80
|
+
{
|
|
81
|
+
"permissions": {
|
|
82
|
+
"allow": [
|
|
83
|
+
"Bash(claudemol*)",
|
|
84
|
+
"Bash(*python*claudemol*)",
|
|
85
|
+
"Bash(pymol*)"
|
|
86
|
+
]
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
```
|
|
64
90
|
|
|
65
91
|
## Usage
|
|
66
92
|
|
|
@@ -72,15 +98,15 @@ Simply ask Claude to open PyMOL or load a structure:
|
|
|
72
98
|
- "Load PDB 4HHB and show as cartoon"
|
|
73
99
|
- "Fetch 1UBQ from the PDB"
|
|
74
100
|
|
|
75
|
-
Claude
|
|
101
|
+
Claude connects to an existing PyMOL if one is running, or launches a new instance.
|
|
76
102
|
|
|
77
103
|
### Example Commands
|
|
78
104
|
|
|
79
105
|
- "Color the protein by secondary structure"
|
|
80
|
-
- "Show the binding site residues within
|
|
106
|
+
- "Show the binding site residues within 5A of the ligand as sticks"
|
|
81
107
|
- "Align these two structures and calculate RMSD"
|
|
82
108
|
- "Create a publication-quality figure with ray tracing"
|
|
83
|
-
- "Make a 360
|
|
109
|
+
- "Make a 360 degree rotation movie"
|
|
84
110
|
|
|
85
111
|
### PyMOL Console Commands
|
|
86
112
|
|
|
@@ -94,7 +120,7 @@ claude_start # Start the listener
|
|
|
94
120
|
|
|
95
121
|
### Available Skills
|
|
96
122
|
|
|
97
|
-
|
|
123
|
+
The plugin includes skills for common workflows:
|
|
98
124
|
|
|
99
125
|
- **pymol-fundamentals** - Basic visualization, selections, coloring
|
|
100
126
|
- **protein-structure-basics** - Secondary structure, B-factor, representations
|
|
@@ -104,6 +130,19 @@ Claude Code has built-in skills for common workflows:
|
|
|
104
130
|
- **publication-figures** - High-quality figure export
|
|
105
131
|
- **movie-creation** - Animations and rotations
|
|
106
132
|
|
|
133
|
+
## How It Works
|
|
134
|
+
|
|
135
|
+
### Connection Lifecycle
|
|
136
|
+
|
|
137
|
+
1. On session start, a hook runs `claudemol status` to check if PyMOL is reachable
|
|
138
|
+
2. When you ask Claude to work with PyMOL, it uses `connect_or_launch()` — connecting to an existing instance or starting a new one
|
|
139
|
+
3. Commands are sent as Python code over TCP and executed inside PyMOL via the socket plugin
|
|
140
|
+
4. If the connection drops, `conn.execute()` auto-reconnects (up to 3 attempts)
|
|
141
|
+
|
|
142
|
+
### Venv Support
|
|
143
|
+
|
|
144
|
+
`claudemol setup` saves your Python interpreter path to `~/.claudemol/config.json`. This means claudemol works even when installed in a project virtualenv — the SessionStart hook and skills read the config to find the right Python.
|
|
145
|
+
|
|
107
146
|
## Troubleshooting
|
|
108
147
|
|
|
109
148
|
### Connection Issues
|
|
@@ -117,18 +156,30 @@ Claude Code has built-in skills for common workflows:
|
|
|
117
156
|
- Run `claudemol setup` to configure PyMOL
|
|
118
157
|
- Check PyMOL's output for any error messages on startup
|
|
119
158
|
|
|
159
|
+
### claudemol Not Found
|
|
160
|
+
|
|
161
|
+
If Claude reports `ModuleNotFoundError`, claudemol may be installed in a venv that isn't active. Fix:
|
|
162
|
+
|
|
163
|
+
```bash
|
|
164
|
+
# Re-run setup from the venv that has claudemol
|
|
165
|
+
.venv/bin/claudemol setup
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
This updates `~/.claudemol/config.json` so future sessions find it.
|
|
169
|
+
|
|
120
170
|
### First-Time Setup Help
|
|
121
171
|
|
|
122
|
-
Run
|
|
172
|
+
Run `/pymol-setup` in Claude Code for guided setup assistance.
|
|
123
173
|
|
|
124
174
|
## Configuration
|
|
125
175
|
|
|
126
|
-
The default socket port is **9880**. Both the plugin and
|
|
176
|
+
The default socket port is **9880**. Both the plugin and connection module use this port.
|
|
127
177
|
|
|
128
178
|
Key files:
|
|
129
|
-
- `
|
|
179
|
+
- `~/.pymolrc` - PyMOL startup script (loads the socket plugin)
|
|
180
|
+
- `~/.claudemol/config.json` - Persisted Python path for venv discovery
|
|
181
|
+
- `src/claudemol/plugin.py` - Socket listener plugin (runs inside PyMOL)
|
|
130
182
|
- `src/claudemol/connection.py` - Python module for socket communication
|
|
131
|
-
- `claude-plugin/skills/` - Claude Code skills for PyMOL workflows
|
|
132
183
|
|
|
133
184
|
## Limitations
|
|
134
185
|
|
|
@@ -8,6 +8,7 @@ Control PyMOL through natural language using Claude Code. This integration enabl
|
|
|
8
8
|
- **Direct socket communication**: Claude Code talks directly to PyMOL (no intermediary server)
|
|
9
9
|
- **Full PyMOL access**: Manipulate representations, colors, views, perform measurements, alignments, and more
|
|
10
10
|
- **Skill-based workflows**: Built-in skills for common tasks like binding site visualization and publication figures
|
|
11
|
+
- **Connect to anything**: Because Claude is the bridge, it can pull in data from online databases (UniProt, PDB, OPM), literature, protein language model annotations, or local analysis scripts and map them directly onto your structure
|
|
11
12
|
|
|
12
13
|
## Architecture
|
|
13
14
|
|
|
@@ -23,22 +24,47 @@ Claude Code → TCP Socket (port 9880) → PyMOL Plugin → cmd.* execution
|
|
|
23
24
|
- [Claude Code](https://docs.anthropic.com/en/docs/claude-code) CLI installed
|
|
24
25
|
- Python 3.10+
|
|
25
26
|
|
|
26
|
-
###
|
|
27
|
+
### 1. Install claudemol
|
|
27
28
|
|
|
28
29
|
```bash
|
|
29
30
|
pip install claudemol
|
|
30
31
|
claudemol setup
|
|
31
32
|
```
|
|
32
33
|
|
|
33
|
-
This
|
|
34
|
+
This configures PyMOL to auto-load the socket plugin and saves your Python path to `~/.claudemol/config.json` so future Claude Code sessions can find it automatically.
|
|
34
35
|
|
|
35
|
-
###
|
|
36
|
+
### 2. Install the Claude Code plugin
|
|
37
|
+
|
|
38
|
+
```
|
|
39
|
+
/plugin marketplace add ANaka/claudemol?path=claude-plugin
|
|
40
|
+
/plugin install claudemol-skills
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
This gives Claude the skills and hooks to work with PyMOL.
|
|
44
|
+
|
|
45
|
+
### 3. Start using it
|
|
36
46
|
|
|
37
47
|
Open Claude Code and say:
|
|
38
48
|
|
|
39
49
|
> "Open PyMOL and load structure 1UBQ"
|
|
40
50
|
|
|
41
|
-
Claude will launch PyMOL
|
|
51
|
+
Claude will launch PyMOL, connect via socket, and load the structure.
|
|
52
|
+
|
|
53
|
+
### Optional: Seamless permissions
|
|
54
|
+
|
|
55
|
+
By default, Claude asks for approval before running each command. To auto-approve PyMOL-related commands, add to your project's `.claude/settings.json`:
|
|
56
|
+
|
|
57
|
+
```json
|
|
58
|
+
{
|
|
59
|
+
"permissions": {
|
|
60
|
+
"allow": [
|
|
61
|
+
"Bash(claudemol*)",
|
|
62
|
+
"Bash(*python*claudemol*)",
|
|
63
|
+
"Bash(pymol*)"
|
|
64
|
+
]
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
```
|
|
42
68
|
|
|
43
69
|
## Usage
|
|
44
70
|
|
|
@@ -50,15 +76,15 @@ Simply ask Claude to open PyMOL or load a structure:
|
|
|
50
76
|
- "Load PDB 4HHB and show as cartoon"
|
|
51
77
|
- "Fetch 1UBQ from the PDB"
|
|
52
78
|
|
|
53
|
-
Claude
|
|
79
|
+
Claude connects to an existing PyMOL if one is running, or launches a new instance.
|
|
54
80
|
|
|
55
81
|
### Example Commands
|
|
56
82
|
|
|
57
83
|
- "Color the protein by secondary structure"
|
|
58
|
-
- "Show the binding site residues within
|
|
84
|
+
- "Show the binding site residues within 5A of the ligand as sticks"
|
|
59
85
|
- "Align these two structures and calculate RMSD"
|
|
60
86
|
- "Create a publication-quality figure with ray tracing"
|
|
61
|
-
- "Make a 360
|
|
87
|
+
- "Make a 360 degree rotation movie"
|
|
62
88
|
|
|
63
89
|
### PyMOL Console Commands
|
|
64
90
|
|
|
@@ -72,7 +98,7 @@ claude_start # Start the listener
|
|
|
72
98
|
|
|
73
99
|
### Available Skills
|
|
74
100
|
|
|
75
|
-
|
|
101
|
+
The plugin includes skills for common workflows:
|
|
76
102
|
|
|
77
103
|
- **pymol-fundamentals** - Basic visualization, selections, coloring
|
|
78
104
|
- **protein-structure-basics** - Secondary structure, B-factor, representations
|
|
@@ -82,6 +108,19 @@ Claude Code has built-in skills for common workflows:
|
|
|
82
108
|
- **publication-figures** - High-quality figure export
|
|
83
109
|
- **movie-creation** - Animations and rotations
|
|
84
110
|
|
|
111
|
+
## How It Works
|
|
112
|
+
|
|
113
|
+
### Connection Lifecycle
|
|
114
|
+
|
|
115
|
+
1. On session start, a hook runs `claudemol status` to check if PyMOL is reachable
|
|
116
|
+
2. When you ask Claude to work with PyMOL, it uses `connect_or_launch()` — connecting to an existing instance or starting a new one
|
|
117
|
+
3. Commands are sent as Python code over TCP and executed inside PyMOL via the socket plugin
|
|
118
|
+
4. If the connection drops, `conn.execute()` auto-reconnects (up to 3 attempts)
|
|
119
|
+
|
|
120
|
+
### Venv Support
|
|
121
|
+
|
|
122
|
+
`claudemol setup` saves your Python interpreter path to `~/.claudemol/config.json`. This means claudemol works even when installed in a project virtualenv — the SessionStart hook and skills read the config to find the right Python.
|
|
123
|
+
|
|
85
124
|
## Troubleshooting
|
|
86
125
|
|
|
87
126
|
### Connection Issues
|
|
@@ -95,18 +134,30 @@ Claude Code has built-in skills for common workflows:
|
|
|
95
134
|
- Run `claudemol setup` to configure PyMOL
|
|
96
135
|
- Check PyMOL's output for any error messages on startup
|
|
97
136
|
|
|
137
|
+
### claudemol Not Found
|
|
138
|
+
|
|
139
|
+
If Claude reports `ModuleNotFoundError`, claudemol may be installed in a venv that isn't active. Fix:
|
|
140
|
+
|
|
141
|
+
```bash
|
|
142
|
+
# Re-run setup from the venv that has claudemol
|
|
143
|
+
.venv/bin/claudemol setup
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
This updates `~/.claudemol/config.json` so future sessions find it.
|
|
147
|
+
|
|
98
148
|
### First-Time Setup Help
|
|
99
149
|
|
|
100
|
-
Run
|
|
150
|
+
Run `/pymol-setup` in Claude Code for guided setup assistance.
|
|
101
151
|
|
|
102
152
|
## Configuration
|
|
103
153
|
|
|
104
|
-
The default socket port is **9880**. Both the plugin and
|
|
154
|
+
The default socket port is **9880**. Both the plugin and connection module use this port.
|
|
105
155
|
|
|
106
156
|
Key files:
|
|
107
|
-
- `
|
|
157
|
+
- `~/.pymolrc` - PyMOL startup script (loads the socket plugin)
|
|
158
|
+
- `~/.claudemol/config.json` - Persisted Python path for venv discovery
|
|
159
|
+
- `src/claudemol/plugin.py` - Socket listener plugin (runs inside PyMOL)
|
|
108
160
|
- `src/claudemol/connection.py` - Python module for socket communication
|
|
109
|
-
- `claude-plugin/skills/` - Claude Code skills for PyMOL workflows
|
|
110
161
|
|
|
111
162
|
## Limitations
|
|
112
163
|
|
|
@@ -6,19 +6,22 @@ Connect to PyMOL via socket for AI-assisted molecular visualization.
|
|
|
6
6
|
|
|
7
7
|
from claudemol.connection import (
|
|
8
8
|
PyMOLConnection,
|
|
9
|
+
check_pymol_installed,
|
|
9
10
|
connect_or_launch,
|
|
10
|
-
launch_pymol,
|
|
11
11
|
find_pymol_command,
|
|
12
|
-
|
|
12
|
+
get_config,
|
|
13
|
+
get_configured_python,
|
|
14
|
+
launch_pymol,
|
|
15
|
+
save_config,
|
|
13
16
|
)
|
|
14
17
|
from claudemol.session import (
|
|
15
18
|
PyMOLSession,
|
|
16
|
-
get_session,
|
|
17
19
|
ensure_running,
|
|
20
|
+
get_session,
|
|
18
21
|
stop_pymol,
|
|
19
22
|
)
|
|
20
23
|
|
|
21
|
-
__version__ = "0.
|
|
24
|
+
__version__ = "0.4.0"
|
|
22
25
|
__all__ = [
|
|
23
26
|
"PyMOLConnection",
|
|
24
27
|
"PyMOLSession",
|
|
@@ -26,6 +29,9 @@ __all__ = [
|
|
|
26
29
|
"launch_pymol",
|
|
27
30
|
"find_pymol_command",
|
|
28
31
|
"check_pymol_installed",
|
|
32
|
+
"get_config",
|
|
33
|
+
"save_config",
|
|
34
|
+
"get_configured_python",
|
|
29
35
|
"get_session",
|
|
30
36
|
"ensure_running",
|
|
31
37
|
"stop_pymol",
|
|
@@ -0,0 +1,304 @@
|
|
|
1
|
+
"""
|
|
2
|
+
CLI for claudemol setup and management.
|
|
3
|
+
|
|
4
|
+
Usage:
|
|
5
|
+
claudemol setup # Configure PyMOL to auto-load the socket plugin
|
|
6
|
+
claudemol status # Check if PyMOL is running and connected
|
|
7
|
+
claudemol test # Test the connection
|
|
8
|
+
claudemol info # Show installation info
|
|
9
|
+
claudemol launch # Launch PyMOL or connect to existing instance
|
|
10
|
+
claudemol exec # Execute code in PyMOL
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
import argparse
|
|
14
|
+
import os
|
|
15
|
+
import stat
|
|
16
|
+
import sys
|
|
17
|
+
from pathlib import Path
|
|
18
|
+
|
|
19
|
+
from claudemol.connection import (
|
|
20
|
+
CONFIG_FILE,
|
|
21
|
+
PyMOLConnection,
|
|
22
|
+
check_pymol_installed,
|
|
23
|
+
connect_or_launch,
|
|
24
|
+
find_pymol_command,
|
|
25
|
+
get_config,
|
|
26
|
+
get_configured_python,
|
|
27
|
+
get_plugin_path,
|
|
28
|
+
save_config,
|
|
29
|
+
)
|
|
30
|
+
|
|
31
|
+
WRAPPER_DIR = Path.home() / ".claudemol" / "bin"
|
|
32
|
+
WRAPPER_PATH = WRAPPER_DIR / "claudemol"
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def _create_wrapper_script():
|
|
36
|
+
"""Create ~/.claudemol/bin/claudemol shell wrapper with baked Python path."""
|
|
37
|
+
WRAPPER_DIR.mkdir(parents=True, exist_ok=True)
|
|
38
|
+
python_path = sys.executable
|
|
39
|
+
script = f"""#!/bin/bash
|
|
40
|
+
exec "{python_path}" -m claudemol.cli "$@"
|
|
41
|
+
"""
|
|
42
|
+
WRAPPER_PATH.write_text(script)
|
|
43
|
+
WRAPPER_PATH.chmod(
|
|
44
|
+
WRAPPER_PATH.stat().st_mode | stat.S_IXUSR | stat.S_IXGRP | stat.S_IXOTH
|
|
45
|
+
)
|
|
46
|
+
return python_path
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def setup_pymol():
|
|
50
|
+
"""Configure PyMOL to auto-load the socket plugin."""
|
|
51
|
+
plugin_path = get_plugin_path()
|
|
52
|
+
if not plugin_path.exists():
|
|
53
|
+
print(f"Error: Plugin not found at {plugin_path}", file=sys.stderr)
|
|
54
|
+
return 1
|
|
55
|
+
|
|
56
|
+
pymolrc_path = Path.home() / ".pymolrc"
|
|
57
|
+
|
|
58
|
+
# Check if already configured
|
|
59
|
+
if pymolrc_path.exists():
|
|
60
|
+
content = pymolrc_path.read_text()
|
|
61
|
+
if "claudemol" in content or "claude_socket_plugin" in content:
|
|
62
|
+
print("PyMOL already configured for claudemol.")
|
|
63
|
+
print(f"Plugin: {plugin_path}")
|
|
64
|
+
# Still save config (in case Python path changed)
|
|
65
|
+
save_config({"python_path": sys.executable})
|
|
66
|
+
print(f"Saved Python path: {sys.executable}")
|
|
67
|
+
# Create/update wrapper script
|
|
68
|
+
_create_wrapper_script()
|
|
69
|
+
print(f"Wrapper script: {WRAPPER_PATH}")
|
|
70
|
+
return 0
|
|
71
|
+
|
|
72
|
+
# Add to .pymolrc
|
|
73
|
+
run_command = f"\n# claudemol: Claude Code integration\nrun {plugin_path}\n"
|
|
74
|
+
|
|
75
|
+
if pymolrc_path.exists():
|
|
76
|
+
with open(pymolrc_path, "a") as f:
|
|
77
|
+
f.write(run_command)
|
|
78
|
+
print(f"Added claudemol plugin to existing {pymolrc_path}")
|
|
79
|
+
else:
|
|
80
|
+
pymolrc_path.write_text(run_command.lstrip())
|
|
81
|
+
print(f"Created {pymolrc_path} with claudemol plugin")
|
|
82
|
+
|
|
83
|
+
print(f"Plugin path: {plugin_path}")
|
|
84
|
+
print("\nSetup complete! The plugin will auto-load when you start PyMOL.")
|
|
85
|
+
|
|
86
|
+
# Check if PyMOL is installed
|
|
87
|
+
if not check_pymol_installed():
|
|
88
|
+
print("\nNote: PyMOL not found in PATH.")
|
|
89
|
+
print("Install PyMOL with one of:")
|
|
90
|
+
print(" - pip install pymol-open-source-whl")
|
|
91
|
+
print(" - brew install pymol (macOS)")
|
|
92
|
+
print(" - Download from https://pymol.org")
|
|
93
|
+
|
|
94
|
+
# Save Python path for SessionStart hook and skills
|
|
95
|
+
save_config({"python_path": sys.executable})
|
|
96
|
+
print(f"Saved Python path: {sys.executable}")
|
|
97
|
+
|
|
98
|
+
# Create wrapper script
|
|
99
|
+
_create_wrapper_script()
|
|
100
|
+
print(f"Wrapper script: {WRAPPER_PATH}")
|
|
101
|
+
|
|
102
|
+
return 0
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
def check_status():
|
|
106
|
+
"""Check PyMOL connection status."""
|
|
107
|
+
print("Checking PyMOL status...")
|
|
108
|
+
|
|
109
|
+
# Show configured Python if available
|
|
110
|
+
configured_python = get_configured_python()
|
|
111
|
+
if configured_python:
|
|
112
|
+
print(f"Configured Python: {configured_python}")
|
|
113
|
+
|
|
114
|
+
# Check if PyMOL is installed
|
|
115
|
+
pymol_cmd = find_pymol_command()
|
|
116
|
+
if pymol_cmd:
|
|
117
|
+
print(f"PyMOL found: {' '.join(pymol_cmd)}")
|
|
118
|
+
else:
|
|
119
|
+
print("PyMOL not found in PATH")
|
|
120
|
+
return 1
|
|
121
|
+
|
|
122
|
+
# Try to connect
|
|
123
|
+
conn = PyMOLConnection()
|
|
124
|
+
try:
|
|
125
|
+
conn.connect(timeout=2.0)
|
|
126
|
+
print("Socket connection: OK (port 9880)")
|
|
127
|
+
conn.disconnect()
|
|
128
|
+
return 0
|
|
129
|
+
except ConnectionError:
|
|
130
|
+
print("Socket connection: Not available")
|
|
131
|
+
print(" (PyMOL may not be running, or plugin not loaded)")
|
|
132
|
+
return 1
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
def test_connection():
|
|
136
|
+
"""Test the PyMOL connection with a simple command."""
|
|
137
|
+
conn = PyMOLConnection()
|
|
138
|
+
try:
|
|
139
|
+
conn.connect(timeout=2.0)
|
|
140
|
+
result = conn.execute("print('claudemol connection test')")
|
|
141
|
+
print("Connection test: OK")
|
|
142
|
+
print(f"Response: {result}")
|
|
143
|
+
conn.disconnect()
|
|
144
|
+
return 0
|
|
145
|
+
except ConnectionError as e:
|
|
146
|
+
print(f"Connection failed: {e}", file=sys.stderr)
|
|
147
|
+
print("\nMake sure PyMOL is running with the socket plugin.")
|
|
148
|
+
print("Start PyMOL and run: claude_status")
|
|
149
|
+
return 1
|
|
150
|
+
except Exception as e:
|
|
151
|
+
print(f"Error: {e}", file=sys.stderr)
|
|
152
|
+
return 1
|
|
153
|
+
|
|
154
|
+
|
|
155
|
+
def show_info():
|
|
156
|
+
"""Show claudemol installation info."""
|
|
157
|
+
plugin_path = get_plugin_path()
|
|
158
|
+
pymolrc_path = Path.home() / ".pymolrc"
|
|
159
|
+
|
|
160
|
+
print("claudemol installation info:")
|
|
161
|
+
print(f" Plugin: {plugin_path}")
|
|
162
|
+
print(f" Plugin exists: {plugin_path.exists()}")
|
|
163
|
+
print(f" .pymolrc: {pymolrc_path}")
|
|
164
|
+
print(f" .pymolrc exists: {pymolrc_path.exists()}")
|
|
165
|
+
|
|
166
|
+
if pymolrc_path.exists():
|
|
167
|
+
content = pymolrc_path.read_text()
|
|
168
|
+
configured = "claudemol" in content or "claude_socket_plugin" in content
|
|
169
|
+
print(f" Configured in .pymolrc: {configured}")
|
|
170
|
+
|
|
171
|
+
pymol_cmd = find_pymol_command()
|
|
172
|
+
print(f" PyMOL command: {' '.join(pymol_cmd) if pymol_cmd else 'not found'}")
|
|
173
|
+
|
|
174
|
+
print(f" Config file: {CONFIG_FILE}")
|
|
175
|
+
config = get_config()
|
|
176
|
+
if config:
|
|
177
|
+
for key, value in config.items():
|
|
178
|
+
print(f" Config {key}: {value}")
|
|
179
|
+
else:
|
|
180
|
+
print(" Config: not set (run 'claudemol setup' to configure)")
|
|
181
|
+
|
|
182
|
+
print(f" Wrapper script: {WRAPPER_PATH}")
|
|
183
|
+
print(f" Wrapper exists: {WRAPPER_PATH.exists()}")
|
|
184
|
+
|
|
185
|
+
|
|
186
|
+
def do_launch(args):
|
|
187
|
+
"""Launch PyMOL or connect to existing instance."""
|
|
188
|
+
file_path = getattr(args, "file", None)
|
|
189
|
+
try:
|
|
190
|
+
conn, process = connect_or_launch(file_path=file_path)
|
|
191
|
+
if process:
|
|
192
|
+
print(f"Launched PyMOL (pid {process.pid})")
|
|
193
|
+
else:
|
|
194
|
+
print("Connected to existing PyMOL instance")
|
|
195
|
+
conn.disconnect()
|
|
196
|
+
return 0
|
|
197
|
+
except Exception as e:
|
|
198
|
+
print(f"Error: {e}", file=sys.stderr)
|
|
199
|
+
return 1
|
|
200
|
+
|
|
201
|
+
|
|
202
|
+
def do_exec(args):
|
|
203
|
+
"""Execute code in PyMOL."""
|
|
204
|
+
code = getattr(args, "code", None)
|
|
205
|
+
|
|
206
|
+
# Read from positional arg or stdin
|
|
207
|
+
if code:
|
|
208
|
+
code = code
|
|
209
|
+
elif not os.isatty(sys.stdin.fileno()):
|
|
210
|
+
code = sys.stdin.read()
|
|
211
|
+
else:
|
|
212
|
+
print(
|
|
213
|
+
"Error: No code provided. Pass as argument or pipe via stdin.",
|
|
214
|
+
file=sys.stderr,
|
|
215
|
+
)
|
|
216
|
+
print(" claudemol exec \"cmd.fetch('1ubq')\"", file=sys.stderr)
|
|
217
|
+
print(" echo \"cmd.fetch('1ubq')\" | claudemol exec", file=sys.stderr)
|
|
218
|
+
return 1
|
|
219
|
+
|
|
220
|
+
if not code.strip():
|
|
221
|
+
print("Error: Empty code.", file=sys.stderr)
|
|
222
|
+
return 1
|
|
223
|
+
|
|
224
|
+
conn = PyMOLConnection()
|
|
225
|
+
try:
|
|
226
|
+
conn.connect(timeout=2.0)
|
|
227
|
+
except ConnectionError:
|
|
228
|
+
print("Error: Cannot connect to PyMOL. Is it running?", file=sys.stderr)
|
|
229
|
+
print(" Run: claudemol launch", file=sys.stderr)
|
|
230
|
+
return 1
|
|
231
|
+
|
|
232
|
+
try:
|
|
233
|
+
result = conn.execute(code)
|
|
234
|
+
if result:
|
|
235
|
+
print(result, end="" if result.endswith("\n") else "\n")
|
|
236
|
+
conn.disconnect()
|
|
237
|
+
return 0
|
|
238
|
+
except Exception as e:
|
|
239
|
+
print(f"Error: {e}", file=sys.stderr)
|
|
240
|
+
conn.disconnect()
|
|
241
|
+
return 1
|
|
242
|
+
|
|
243
|
+
|
|
244
|
+
def main():
|
|
245
|
+
parser = argparse.ArgumentParser(
|
|
246
|
+
description="claudemol: PyMOL integration for Claude Code",
|
|
247
|
+
formatter_class=argparse.RawDescriptionHelpFormatter,
|
|
248
|
+
)
|
|
249
|
+
|
|
250
|
+
subparsers = parser.add_subparsers(dest="command")
|
|
251
|
+
|
|
252
|
+
# setup
|
|
253
|
+
subparsers.add_parser(
|
|
254
|
+
"setup", help="Configure PyMOL to auto-load the socket plugin"
|
|
255
|
+
)
|
|
256
|
+
|
|
257
|
+
# status
|
|
258
|
+
subparsers.add_parser("status", help="Check if PyMOL is running and connected")
|
|
259
|
+
|
|
260
|
+
# test
|
|
261
|
+
subparsers.add_parser("test", help="Test the connection with a simple command")
|
|
262
|
+
|
|
263
|
+
# info
|
|
264
|
+
subparsers.add_parser("info", help="Show installation info")
|
|
265
|
+
|
|
266
|
+
# launch
|
|
267
|
+
launch_parser = subparsers.add_parser(
|
|
268
|
+
"launch", help="Launch PyMOL or connect to existing instance"
|
|
269
|
+
)
|
|
270
|
+
launch_parser.add_argument(
|
|
271
|
+
"file", nargs="?", default=None, help="File to open (e.g., .pdb, .cif)"
|
|
272
|
+
)
|
|
273
|
+
|
|
274
|
+
# exec
|
|
275
|
+
exec_parser = subparsers.add_parser("exec", help="Execute code in PyMOL")
|
|
276
|
+
exec_parser.add_argument(
|
|
277
|
+
"code",
|
|
278
|
+
nargs="?",
|
|
279
|
+
default=None,
|
|
280
|
+
help="Python code to execute (or pipe via stdin)",
|
|
281
|
+
)
|
|
282
|
+
|
|
283
|
+
args = parser.parse_args()
|
|
284
|
+
|
|
285
|
+
if args.command is None:
|
|
286
|
+
show_info()
|
|
287
|
+
return 0
|
|
288
|
+
elif args.command == "setup":
|
|
289
|
+
return setup_pymol()
|
|
290
|
+
elif args.command == "status":
|
|
291
|
+
return check_status()
|
|
292
|
+
elif args.command == "test":
|
|
293
|
+
return test_connection()
|
|
294
|
+
elif args.command == "info":
|
|
295
|
+
show_info()
|
|
296
|
+
return 0
|
|
297
|
+
elif args.command == "launch":
|
|
298
|
+
return do_launch(args)
|
|
299
|
+
elif args.command == "exec":
|
|
300
|
+
return do_exec(args)
|
|
301
|
+
|
|
302
|
+
|
|
303
|
+
if __name__ == "__main__":
|
|
304
|
+
sys.exit(main())
|
|
@@ -17,6 +17,9 @@ DEFAULT_PORT = 9880
|
|
|
17
17
|
CONNECT_TIMEOUT = 5.0
|
|
18
18
|
RECV_TIMEOUT = 30.0
|
|
19
19
|
|
|
20
|
+
CONFIG_DIR = Path.home() / ".claudemol"
|
|
21
|
+
CONFIG_FILE = CONFIG_DIR / "config.json"
|
|
22
|
+
|
|
20
23
|
# Common PyMOL installation paths
|
|
21
24
|
PYMOL_PATHS = [
|
|
22
25
|
# uv environment (created by /pymol-setup)
|
|
@@ -247,3 +250,28 @@ def connect_or_launch(file_path=None):
|
|
|
247
250
|
process = launch_pymol(file_path=file_path)
|
|
248
251
|
conn.connect()
|
|
249
252
|
return conn, process
|
|
253
|
+
|
|
254
|
+
|
|
255
|
+
def get_config():
|
|
256
|
+
"""Read persisted claudemol config."""
|
|
257
|
+
if CONFIG_FILE.exists():
|
|
258
|
+
try:
|
|
259
|
+
return json.loads(CONFIG_FILE.read_text())
|
|
260
|
+
except (json.JSONDecodeError, OSError):
|
|
261
|
+
return {}
|
|
262
|
+
return {}
|
|
263
|
+
|
|
264
|
+
|
|
265
|
+
def save_config(config):
|
|
266
|
+
"""Save claudemol config."""
|
|
267
|
+
CONFIG_DIR.mkdir(parents=True, exist_ok=True)
|
|
268
|
+
CONFIG_FILE.write_text(json.dumps(config, indent=2) + "\n")
|
|
269
|
+
|
|
270
|
+
|
|
271
|
+
def get_configured_python():
|
|
272
|
+
"""Get the Python path from persisted config. Returns path string or None."""
|
|
273
|
+
config = get_config()
|
|
274
|
+
python_path = config.get("python_path")
|
|
275
|
+
if python_path and os.path.isfile(python_path) and os.access(python_path, os.X_OK):
|
|
276
|
+
return python_path
|
|
277
|
+
return None
|
|
@@ -1,167 +0,0 @@
|
|
|
1
|
-
"""
|
|
2
|
-
CLI for claudemol setup and management.
|
|
3
|
-
|
|
4
|
-
Usage:
|
|
5
|
-
claudemol setup # Configure PyMOL to auto-load the socket plugin
|
|
6
|
-
claudemol status # Check if PyMOL is running and connected
|
|
7
|
-
claudemol test # Test the connection
|
|
8
|
-
"""
|
|
9
|
-
|
|
10
|
-
import argparse
|
|
11
|
-
import sys
|
|
12
|
-
from pathlib import Path
|
|
13
|
-
|
|
14
|
-
from claudemol.connection import (
|
|
15
|
-
PyMOLConnection,
|
|
16
|
-
check_pymol_installed,
|
|
17
|
-
find_pymol_command,
|
|
18
|
-
get_plugin_path,
|
|
19
|
-
)
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
def setup_pymol():
|
|
23
|
-
"""Configure PyMOL to auto-load the socket plugin."""
|
|
24
|
-
plugin_path = get_plugin_path()
|
|
25
|
-
if not plugin_path.exists():
|
|
26
|
-
print(f"Error: Plugin not found at {plugin_path}", file=sys.stderr)
|
|
27
|
-
return 1
|
|
28
|
-
|
|
29
|
-
pymolrc_path = Path.home() / ".pymolrc"
|
|
30
|
-
|
|
31
|
-
# Check if already configured
|
|
32
|
-
if pymolrc_path.exists():
|
|
33
|
-
content = pymolrc_path.read_text()
|
|
34
|
-
if "claudemol" in content or "claude_socket_plugin" in content:
|
|
35
|
-
print("PyMOL already configured for claudemol.")
|
|
36
|
-
print(f"Plugin: {plugin_path}")
|
|
37
|
-
return 0
|
|
38
|
-
|
|
39
|
-
# Add to .pymolrc
|
|
40
|
-
run_command = f'\n# claudemol: Claude Code integration\nrun {plugin_path}\n'
|
|
41
|
-
|
|
42
|
-
if pymolrc_path.exists():
|
|
43
|
-
with open(pymolrc_path, "a") as f:
|
|
44
|
-
f.write(run_command)
|
|
45
|
-
print(f"Added claudemol plugin to existing {pymolrc_path}")
|
|
46
|
-
else:
|
|
47
|
-
pymolrc_path.write_text(run_command.lstrip())
|
|
48
|
-
print(f"Created {pymolrc_path} with claudemol plugin")
|
|
49
|
-
|
|
50
|
-
print(f"Plugin path: {plugin_path}")
|
|
51
|
-
print("\nSetup complete! The plugin will auto-load when you start PyMOL.")
|
|
52
|
-
|
|
53
|
-
# Check if PyMOL is installed
|
|
54
|
-
if not check_pymol_installed():
|
|
55
|
-
print("\nNote: PyMOL not found in PATH.")
|
|
56
|
-
print("Install PyMOL with one of:")
|
|
57
|
-
print(" - pip install pymol-open-source-whl")
|
|
58
|
-
print(" - brew install pymol (macOS)")
|
|
59
|
-
print(" - Download from https://pymol.org")
|
|
60
|
-
|
|
61
|
-
return 0
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
def check_status():
|
|
65
|
-
"""Check PyMOL connection status."""
|
|
66
|
-
print("Checking PyMOL status...")
|
|
67
|
-
|
|
68
|
-
# Check if PyMOL is installed
|
|
69
|
-
pymol_cmd = find_pymol_command()
|
|
70
|
-
if pymol_cmd:
|
|
71
|
-
print(f"PyMOL found: {' '.join(pymol_cmd)}")
|
|
72
|
-
else:
|
|
73
|
-
print("PyMOL not found in PATH")
|
|
74
|
-
return 1
|
|
75
|
-
|
|
76
|
-
# Try to connect
|
|
77
|
-
conn = PyMOLConnection()
|
|
78
|
-
try:
|
|
79
|
-
conn.connect(timeout=2.0)
|
|
80
|
-
print("Socket connection: OK (port 9880)")
|
|
81
|
-
conn.disconnect()
|
|
82
|
-
return 0
|
|
83
|
-
except ConnectionError:
|
|
84
|
-
print("Socket connection: Not available")
|
|
85
|
-
print(" (PyMOL may not be running, or plugin not loaded)")
|
|
86
|
-
return 1
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
def test_connection():
|
|
90
|
-
"""Test the PyMOL connection with a simple command."""
|
|
91
|
-
conn = PyMOLConnection()
|
|
92
|
-
try:
|
|
93
|
-
conn.connect(timeout=2.0)
|
|
94
|
-
result = conn.execute("print('claudemol connection test')")
|
|
95
|
-
print("Connection test: OK")
|
|
96
|
-
print(f"Response: {result}")
|
|
97
|
-
conn.disconnect()
|
|
98
|
-
return 0
|
|
99
|
-
except ConnectionError as e:
|
|
100
|
-
print(f"Connection failed: {e}", file=sys.stderr)
|
|
101
|
-
print("\nMake sure PyMOL is running with the socket plugin.")
|
|
102
|
-
print("Start PyMOL and run: claude_status")
|
|
103
|
-
return 1
|
|
104
|
-
except Exception as e:
|
|
105
|
-
print(f"Error: {e}", file=sys.stderr)
|
|
106
|
-
return 1
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
def show_info():
|
|
110
|
-
"""Show claudemol installation info."""
|
|
111
|
-
plugin_path = get_plugin_path()
|
|
112
|
-
pymolrc_path = Path.home() / ".pymolrc"
|
|
113
|
-
|
|
114
|
-
print("claudemol installation info:")
|
|
115
|
-
print(f" Plugin: {plugin_path}")
|
|
116
|
-
print(f" Plugin exists: {plugin_path.exists()}")
|
|
117
|
-
print(f" .pymolrc: {pymolrc_path}")
|
|
118
|
-
print(f" .pymolrc exists: {pymolrc_path.exists()}")
|
|
119
|
-
|
|
120
|
-
if pymolrc_path.exists():
|
|
121
|
-
content = pymolrc_path.read_text()
|
|
122
|
-
configured = "claudemol" in content or "claude_socket_plugin" in content
|
|
123
|
-
print(f" Configured in .pymolrc: {configured}")
|
|
124
|
-
|
|
125
|
-
pymol_cmd = find_pymol_command()
|
|
126
|
-
print(f" PyMOL command: {' '.join(pymol_cmd) if pymol_cmd else 'not found'}")
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
def main():
|
|
130
|
-
parser = argparse.ArgumentParser(
|
|
131
|
-
description="claudemol: PyMOL integration for Claude Code",
|
|
132
|
-
formatter_class=argparse.RawDescriptionHelpFormatter,
|
|
133
|
-
epilog="""
|
|
134
|
-
Commands:
|
|
135
|
-
setup Configure PyMOL to auto-load the socket plugin
|
|
136
|
-
status Check if PyMOL is running and connected
|
|
137
|
-
test Test the connection with a simple command
|
|
138
|
-
info Show installation info
|
|
139
|
-
|
|
140
|
-
For Claude Code skills, install the claudemol-skills plugin:
|
|
141
|
-
/plugin marketplace add ANaka/claudemol?path=claude-plugin
|
|
142
|
-
/plugin install claudemol-skills
|
|
143
|
-
""",
|
|
144
|
-
)
|
|
145
|
-
parser.add_argument(
|
|
146
|
-
"command",
|
|
147
|
-
nargs="?",
|
|
148
|
-
choices=["setup", "status", "test", "info"],
|
|
149
|
-
default="info",
|
|
150
|
-
help="Command to run",
|
|
151
|
-
)
|
|
152
|
-
|
|
153
|
-
args = parser.parse_args()
|
|
154
|
-
|
|
155
|
-
if args.command == "setup":
|
|
156
|
-
return setup_pymol()
|
|
157
|
-
elif args.command == "status":
|
|
158
|
-
return check_status()
|
|
159
|
-
elif args.command == "test":
|
|
160
|
-
return test_connection()
|
|
161
|
-
elif args.command == "info":
|
|
162
|
-
show_info()
|
|
163
|
-
return 0
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
if __name__ == "__main__":
|
|
167
|
-
sys.exit(main())
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|