Adding a New Tool¶
Step-by-Step¶
1. Choose a Group¶
Decide which group your tool belongs to. Check existing groups in tools/:
tools/
├── io/ # File I/O
├── measurements/ # Distances, angles, contacts
├── representations/ # Visual representations
├── color/ # Coloring schemes
├── analysis/ # Structural analysis
├── interactions/ # Interaction detection
├── visibility/ # Show/hide
├── selection/ # Selection management
├── labels/ # Labels
├── camera/ # Camera controls
├── ...
To create a new group, just create a new directory under tools/ with an __init__.py.
2. Create the Tool File¶
Create tools/<group>/<name>.py:
"""Short description of what the tool does.
Usage: /<group> <name> <arg1> [arg2]
Example: /<group> <name> protein
"""
def run(cmd, selection: str, cutoff: str = "3.5") -> str:
"""Execute the tool."""
# Dry-run support (for testing without the rendering engine)
if cmd is None:
return f"[dry-run] {selection} cutoff={cutoff}"
# Convert string args to proper types
cutoff_val = float(cutoff)
# Call rendering API
result = cmd.some_function(selection, cutoff=cutoff_val)
return f"Result: {result}"
All arguments are strings
The parser passes all arguments as strings. Convert to float, int, etc. inside run().
3. Test with Dry-Run¶
Run the test suite — your tool is automatically included:
The conftest.py fixture discovers all tools and runs dry-run tests (with cmd=None) automatically via parametrized tests.
4. Add a Shortcut (Optional)¶
If your tool deserves a top-level shortcut, add it to expand_shortcut() in codemol/app/command_dispatcher.py:
def expand_shortcut(text: str) -> str:
# ... existing shortcuts ...
if text.startswith("/myshortcut"):
return text.replace("/myshortcut", "/<group> <tool>", 1)
return text
5. Add Autocomplete Entry (Optional)¶
Add your command to build_command_list() in the same file:
def build_command_list() -> list[tuple[str, str]]:
return [
# ... existing entries ...
("/<group> <name>", "Short description"),
]
Conventions¶
Naming¶
- File name = tool name (e.g.,
distance.py→/measurements distance) - Use lowercase, single words when possible
- For multi-word tools, use underscores in filenames (e.g.,
salt_bridges.py)
Docstrings¶
Include usage and example in the module docstring — this is used by the AI agent to understand tool capabilities:
"""Measure the distance between two atoms.
Usage: /measurements distance <atom1> <atom2>
Example: /measurements distance A/45/CA B/120/CA
"""
Return Values¶
- Return a human-readable string summarizing what happened
- Include units where applicable (e.g.,
"Distance: 3.45 Å") - Return error messages as strings (don't raise exceptions for user errors)
Dry-Run Pattern¶
Always support cmd is None for testing:
def run(cmd, selection: str) -> str:
if cmd is None:
return f"[dry-run] would process {selection}"
# Real implementation...
Example: Complete Tool¶
Here's a complete tool that calculates solvent-accessible surface area:
"""Calculate solvent-accessible surface area for a selection.
Usage: /surface area [selection]
Example: /surface area protein
"""
def run(cmd, selection: str = "all") -> str:
if cmd is None:
return f"[dry-run] SASA for {selection}"
area = cmd.get_area(selection)
count = cmd.count_atoms(selection)
return f"SASA: {area:.1f} Ų ({count} atoms)"
That's it — save as tools/surface/area.py and it's immediately available as /surface area.