Skip to main content

View source on GitHub

A plugin can ship code as well as instructions. This example’s city-weather/ plugin bundles a small Python script, and its command and skill tell the agent to run that script and show what it prints. The agent doesn’t work out the API calls itself; it runs one command. Put the work in a script when it should be the same every time: calling an API, parsing a file, or formatting a report. The result is repeatable, uses fewer tokens than having the agent improvise, and you can test the script on its own.

How It Works

  1. OpenHands fetches the plugin into the sandbox, under ~/.openhands/cache/plugins/.
  2. The message /city-weather:now Tokyo triggers the plugin’s command. The agent receives the command’s instructions along with the absolute path of the file they came from.
  3. The instructions say where the script is relative to that file, so the agent runs it by its absolute path and shows the output.

Run It

Use the companion load-plugin example:
The conversation runs the script once and finishes with its report:
To use the skill instead of the command, send a message such as --message "What's the weather in Paris?". The skill is triggered by “weather in”, “weather for”, and “forecast for”, and runs the same script.
To test changes from a branch before they’re merged, pass --ref <branch> to load_plugin.py. For OpenHands Enterprise, pass --base-url with your install’s URL, or rebuild the badges with build_launch_url.py --base-url.

Run the Script Locally

The script uses only the Python standard library and the free Open-Meteo API, which needs no key. Run it directly to check it before the agent does:
If the city isn’t found or Open-Meteo can’t be reached, it prints an error and exits with status 1, and the instructions tell the agent to show that error.

How the Agent Finds the Script

The plugin is fetched to a path in the sandbox that the plugin author can’t know in advance. When a command or skill fires, OpenHands adds the location of its file to what the agent sees:
So write the script’s path relative to the file that refers to it: The command spells out the relationship so the agent doesn’t have to guess:
city-weather/commands/now.md
This works for commands and skills, which the agent reads. Hooks are different: OpenHands runs them from the agent’s workspace with no plugin-root path, so a hook can’t call a script bundled in its plugin. That’s why the hook examples, such as command-blacklist, put their script inline in hooks.json.

Where Scripts Go

This plugin follows the Claude Code plugin layout, which OpenHands loads:
  • A script used by one skill goes in that skill’s directory, under skills/<skill>/scripts/. That’s where weather.py is.
  • A script shared by several skills, or called by hooks, goes in scripts/ at the plugin root.
The command is there so that /city-weather:now and the plugin’s entry_command can start it on launch. In OpenHands, entry_command and /<plugin>:<name> refer to files in commands/, while a skill is triggered by keywords in the message.
Claude Code replaces ${CLAUDE_SKILL_DIR} and ${CLAUDE_PLUGIN_ROOT} with absolute paths when it loads a skill, and its docs show bundled scripts referred to that way. OpenHands doesn’t replace these variables, so the agent would see them as literal text. It gives the agent the skill’s location instead, which is why this plugin’s instructions use paths relative to the file.Claude Code also puts a plugin’s bin/ directory on the PATH. OpenHands doesn’t, so call scripts by their path.

Plugin Structure

load-plugin

Start a conversation with a plugin loaded through the REST API

launch-plugin-badge

Build launch links and badges like the ones above

command-blacklist

A plugin whose script runs as a hook, inlined in hooks.json

Plugins overview

What plugins are and the format they follow

Plugin Launcher

The /launch route the badges use

Claude Code skills

Supporting files in a skill directory, including scripts/