claudemol 0.3.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.3.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
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "claudemol"
3
- version = "0.3.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"
@@ -21,7 +21,7 @@ from claudemol.session import (
21
21
  stop_pymol,
22
22
  )
23
23
 
24
- __version__ = "0.1.0"
24
+ __version__ = "0.4.0"
25
25
  __all__ = [
26
26
  "PyMOLConnection",
27
27
  "PyMOLSession",
@@ -5,9 +5,14 @@ Usage:
5
5
  claudemol setup # Configure PyMOL to auto-load the socket plugin
6
6
  claudemol status # Check if PyMOL is running and connected
7
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
8
11
  """
9
12
 
10
13
  import argparse
14
+ import os
15
+ import stat
11
16
  import sys
12
17
  from pathlib import Path
13
18
 
@@ -15,6 +20,7 @@ from claudemol.connection import (
15
20
  CONFIG_FILE,
16
21
  PyMOLConnection,
17
22
  check_pymol_installed,
23
+ connect_or_launch,
18
24
  find_pymol_command,
19
25
  get_config,
20
26
  get_configured_python,
@@ -22,6 +28,23 @@ from claudemol.connection import (
22
28
  save_config,
23
29
  )
24
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
+
25
48
 
26
49
  def setup_pymol():
27
50
  """Configure PyMOL to auto-load the socket plugin."""
@@ -41,6 +64,9 @@ def setup_pymol():
41
64
  # Still save config (in case Python path changed)
42
65
  save_config({"python_path": sys.executable})
43
66
  print(f"Saved Python path: {sys.executable}")
67
+ # Create/update wrapper script
68
+ _create_wrapper_script()
69
+ print(f"Wrapper script: {WRAPPER_PATH}")
44
70
  return 0
45
71
 
46
72
  # Add to .pymolrc
@@ -69,6 +95,10 @@ def setup_pymol():
69
95
  save_config({"python_path": sys.executable})
70
96
  print(f"Saved Python path: {sys.executable}")
71
97
 
98
+ # Create wrapper script
99
+ _create_wrapper_script()
100
+ print(f"Wrapper script: {WRAPPER_PATH}")
101
+
72
102
  return 0
73
103
 
74
104
 
@@ -149,34 +179,113 @@ def show_info():
149
179
  else:
150
180
  print(" Config: not set (run 'claudemol setup' to configure)")
151
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
+
152
243
 
153
244
  def main():
154
245
  parser = argparse.ArgumentParser(
155
246
  description="claudemol: PyMOL integration for Claude Code",
156
247
  formatter_class=argparse.RawDescriptionHelpFormatter,
157
- epilog="""
158
- Commands:
159
- setup Configure PyMOL to auto-load the socket plugin
160
- status Check if PyMOL is running and connected
161
- test Test the connection with a simple command
162
- info Show installation info
163
-
164
- For Claude Code skills, install the claudemol-skills plugin:
165
- /plugin marketplace add ANaka/claudemol?path=claude-plugin
166
- /plugin install claudemol-skills
167
- """,
168
248
  )
169
- parser.add_argument(
170
- "command",
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",
171
278
  nargs="?",
172
- choices=["setup", "status", "test", "info"],
173
- default="info",
174
- help="Command to run",
279
+ default=None,
280
+ help="Python code to execute (or pipe via stdin)",
175
281
  )
176
282
 
177
283
  args = parser.parse_args()
178
284
 
179
- if args.command == "setup":
285
+ if args.command is None:
286
+ show_info()
287
+ return 0
288
+ elif args.command == "setup":
180
289
  return setup_pymol()
181
290
  elif args.command == "status":
182
291
  return check_status()
@@ -185,6 +294,10 @@ For Claude Code skills, install the claudemol-skills plugin:
185
294
  elif args.command == "info":
186
295
  show_info()
187
296
  return 0
297
+ elif args.command == "launch":
298
+ return do_launch(args)
299
+ elif args.command == "exec":
300
+ return do_exec(args)
188
301
 
189
302
 
190
303
  if __name__ == "__main__":
File without changes
File without changes
File without changes