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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: claudemol
3
- Version: 0.2.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
- ### Installation
49
+ ### 1. Install claudemol
49
50
 
50
51
  ```bash
51
52
  pip install claudemol
52
53
  claudemol setup
53
54
  ```
54
55
 
55
- This installs the package and configures PyMOL to auto-load the socket plugin.
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
- ### Start Using It
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 (with the socket listener active) and load the structure.
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 will launch PyMOL if it's not already running.
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 of the ligand as sticks"
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° rotation movie"
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
- Claude Code has built-in skills for common workflows:
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 the `/pymol-setup` skill in Claude Code for guided setup assistance.
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 Claude Code connection module use this port.
176
+ The default socket port is **9880**. Both the plugin and connection module use this port.
127
177
 
128
178
  Key files:
129
- - `src/claudemol/plugin.py` - PyMOL plugin (auto-loads via pymolrc)
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
- ### Installation
27
+ ### 1. Install claudemol
27
28
 
28
29
  ```bash
29
30
  pip install claudemol
30
31
  claudemol setup
31
32
  ```
32
33
 
33
- This installs the package and configures PyMOL to auto-load the socket plugin.
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
- ### Start Using It
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 (with the socket listener active) and load the structure.
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 will launch PyMOL if it's not already running.
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 of the ligand as sticks"
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° rotation movie"
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
- Claude Code has built-in skills for common workflows:
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 the `/pymol-setup` skill in Claude Code for guided setup assistance.
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 Claude Code connection module use this port.
154
+ The default socket port is **9880**. Both the plugin and connection module use this port.
105
155
 
106
156
  Key files:
107
- - `src/claudemol/plugin.py` - PyMOL plugin (auto-loads via pymolrc)
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
 
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "claudemol"
3
- version = "0.2.0"
3
+ version = "0.4.0"
4
4
  description = "PyMOL integration for Claude Code - control molecular visualization via natural language"
5
5
  readme = "README.md"
6
6
  requires-python = ">=3.10"
@@ -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
- check_pymol_installed,
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.1.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