Skip to content
Developer Preview

The weather plugin

This page shows the complete weather plugin: one skill, one command and one hook in four files. After reading it you can copy the directory, install it, and know what each file is for. If you want to build it step by step instead, Quickstart: your first plugin does exactly that.

The plugin exists to show the smallest useful native plugin, one that uses each of the three everyday capability kinds:

  • A skill, forecast, that teaches the model how to explain a forecast the user already has. It does not fetch weather.
  • A command, forecast-brief, a prompt template you run as /forecast-brief Lisbon tomorrow.
  • A hook, log-tool-use, a shell script that appends one line per tool call to a log file in the plugin data directory.

The skill and the command work as soon as the plugin is installed and enabled. The hook needs your approval first.

weather/
├── .muse-plugin/
│ └── plugin.json
├── skills/
│ └── forecast/
│ └── SKILL.md
├── commands/
│ └── forecast-brief.md
└── hooks/
└── log-tool-use.sh
weather/.muse-plugin/plugin.json
{
"schemaVersion": 1,
"name": "weather",
"displayName": "Weather",
"version": "0.1.0",
"description": "Example plugin: a forecast skill, a forecast-brief command, and a hook that logs tool use.",
"compat": {
"source": "native",
"manifestDir": ".muse-plugin"
},
"capabilities": {
"skills": [
{
"id": "forecast",
"path": "skills/forecast/SKILL.md",
"enabledDefault": true
}
],
"commands": [
{
"id": "forecast-brief",
"path": "commands/forecast-brief.md",
"enabledDefault": true
}
],
"hooks": [
{
"id": "log-tool-use",
"event": "PostToolUse",
"command": ["sh", "hooks/log-tool-use.sh"],
"timeoutMs": 5000,
"statusMessage": "Logging tool use"
}
],
"mcpServers": [],
"reminders": []
}
}

What to look at:

  • name is the plugin id. It is what you pass to every muse plugins command and what prefixes the qualified forms /weather:forecast and /weather:forecast-brief.
  • compat.manifestDir names the directory the manifest lives in. Only the manifest lives there; every path is relative to the plugin root.
  • The hook command is an argv array, run without a shell. Muse Code rewrites exactly one element, the one that spells a relative path declared in the manifest (hooks/log-tool-use.sh), to the installed copy. Nothing else in the array is substituted.
  • mcpServers and reminders are present but empty. Absent arrays count as empty too, so you can leave them out.

Manifest lists every field with its limits.

weather/skills/forecast/SKILL.md
---
name: forecast
description: Explain a weather forecast for a place and date in plain language. Use when the user asks what the weather will be like, whether to bring an umbrella, or how to read a forecast they pasted.
metadata:
short-description: Explain a weather forecast in plain language
---
# Forecast
Turn forecast data the user already has (a pasted forecast, a screenshot they
described, or numbers they typed) into a short, practical answer. This skill
does not fetch live weather; if the user gives you no data, ask for the place,
the date, and where the forecast came from.
## How to answer
1. Restate the place and the date range you are covering.
2. Give the headline in one sentence: for example "Cool and wet, clearing by
evening."
3. List the numbers that matter: high and low temperature, chance of rain,
wind, and anything unusual (frost, heat, storms).
4. End with one practical line: what to wear or carry, or whether to move an
outdoor plan.
Keep the whole answer under 120 words unless the user asks for detail. Use the
units the user used. If the data is incomplete, say what is missing rather
than guessing.

What to look at:

  • description is what the model sees in its skill catalog at session start. It says both what the skill does and when to use it, so the model can pick it without reading the body.
  • metadata.short-description matters when many skills are installed: the startup catalog compresses rows, and a plugin skill without a short description is reduced to its id and path.
  • The body is loaded only when you invoke /forecast or the model decides the skill is relevant. It reads like instructions to a colleague, not like a prompt template.
weather/commands/forecast-brief.md
---
description: Write a two-line forecast brief for a place
argument-hint: <place> [date]
---
Write a two-line weather brief for: $ARGUMENTS
Line 1: the place and date, then the headline conditions.
Line 2: one practical recommendation (what to wear, carry, or reschedule).
Use only forecast data already present in this conversation. If there is none,
reply with one line asking for the forecast source instead of inventing numbers.

What to look at:

  • description and argument-hint drive the / palette in the terminal UI.
  • Every literal $ARGUMENTS in the body is replaced by the text you type after /forecast-brief. The transcript shows only what you typed; the model receives the expanded body.
  • The last paragraph is the guard against invented data. A command is the right place for this kind of instruction because it runs on every use.
weather/hooks/log-tool-use.sh
#!/bin/sh
# PostToolUse hook: append one line per tool call to a log file in the
# plugin's data directory. Reads the JSON payload from stdin, never blocks.
set -u
payload=$(cat)
# Muse Code advertises the data directory but does not create it. Create it
# before writing. Exit quietly if the variable is missing (for example when
# the script is run by hand).
[ -n "${MUSE_PLUGIN_DATA_DIR:-}" ] || exit 0
mkdir -p "$MUSE_PLUGIN_DATA_DIR" || exit 0
tool=$(printf '%s' "$payload" | sed -n 's/.*"tool_name"[[:space:]]*:[[:space:]]*"\([^"]*\)".*/\1/p' | head -n 1)
[ -n "$tool" ] || tool=unknown
printf '%s\t%s\n' "$(date -u +%Y-%m-%dT%H:%M:%SZ)" "$tool" >> "$MUSE_PLUGIN_DATA_DIR/tool-use.log"
exit 0

What to look at:

  • The script reads the whole payload from stdin once. For PostToolUse the payload carries tool_name, tool_input and tool_response among other keys; the script uses only tool_name.
  • MUSE_PLUGIN_DATA_DIR is a writable directory kept per plugin, but it may not exist yet. The mkdir -p line is what makes the first write succeed.
  • The script always exits 0. On PostToolUse an exit code of 2 with text on stderr would send that text to the model as feedback; a logging hook has no reason to do that.
  • The process starts with a cleared environment plus a small allowlist such as HOME and PATH, so the script relies on nothing else.

Hook events and payloads lists every event and the keys each one delivers.

The commands below were run against Muse Code 1.3.0 with an empty plugin store, from the directory that holds the weather copy. Outputs are trimmed and the store path is shown as <data dir>.

Validate the skill directory, then the whole plugin:

Terminal window
muse skills validate weather/skills/forecast
muse plugins validate weather
valid forecast
valid weather native skills=1 commands=1 hooks=1 mcp=0 reminders=0 diagnostics=0

Install and inspect:

Terminal window
muse plugins install weather
muse plugins inspect weather
installed weather 0.1.0 enabled=true trust=user-local provenance=native-local cache=<data dir>/plugins/cache/local/weather/bacc26ed…/package
warning third-party plugin: hooks require review before activation; skills and commands are active without review while the plugin is enabled
weather 0.1.0 enabled=true active=true trust=user-local valid=true skills=1 commands=1 hooks=1 mcp=0 reminders=0 cache=<data dir>/plugins/cache/local/weather/bacc26ed…/package
warning third-party plugin: hooks require review before activation; skills and commands are active without review while the plugin is enabled
runtime-capability plugin:weather:hook:log-tool-use status=review_needed

At this point /forecast and /forecast-brief already work in a session. The hook is waiting for review. Approve it and inspect again:

Terminal window
muse plugins approve weather
muse plugins inspect weather
approve plugin:weather:hook:log-tool-use
weather 0.1.0 enabled=true active=true trust=user-local valid=true skills=1 commands=1 hooks=1 mcp=0 reminders=0 cache=<data dir>/plugins/cache/local/weather/bacc26ed…/package
warning third-party plugin: hooks require review before activation; skills and commands are active without review while the plugin is enabled
runtime-capability plugin:weather:hook:log-tool-use status=trusted_enabled

The warning line is printed for every plugin that declares hooks, approved or not; the runtime-capability row is the real state.

Run the hook against a fixture without opening a session. The fixture carries the event name and the payload the hook would read on stdin:

post-tool-use.json
{
"event": "PostToolUse",
"stdin": {
"hook_event_name": "PostToolUse",
"session_id": "00000000-0000-0000-0000-000000000001",
"cwd": "/tmp/scratch",
"tool_name": "read_file",
"tool_input": { "path": "README.md" },
"tool_response": { "ok": true },
"tool_use_id": "call_9"
}
}
Terminal window
muse plugins hook test weather:log-tool-use --fixture post-tool-use.json
cat "<data dir>/plugins/data/weather/tool-use.log"
hook-test weather:log-tool-use status=completed
2026-09-17T04:32:25Z read_file

status=completed means the script exited 0. The log line shows that the hook found MUSE_PLUGIN_DATA_DIR, created it, and wrote the tool name.

Terminal window
muse plugins remove weather --delete-data
removed weather

--delete-data also deletes the data directory with tool-use.log. Without it the directory stays behind and a later reinstall finds it again. Removing forgets your approval; a reinstalled plugin starts at review_needed.