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.
Purpose
Section titled “Purpose”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.
Directory tree
Section titled “Directory tree”weather/├── .muse-plugin/│ └── plugin.json├── skills/│ └── forecast/│ └── SKILL.md├── commands/│ └── forecast-brief.md└── hooks/ └── log-tool-use.shThe files
Section titled “The files”.muse-plugin/plugin.json
Section titled “.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:
nameis the plugin id. It is what you pass to everymuse pluginscommand and what prefixes the qualified forms/weather:forecastand/weather:forecast-brief.compat.manifestDirnames the directory the manifest lives in. Only the manifest lives there; everypathis relative to the plugin root.- The hook
commandis 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. mcpServersandremindersare present but empty. Absent arrays count as empty too, so you can leave them out.
Manifest lists every field with its limits.
skills/forecast/SKILL.md
Section titled “skills/forecast/SKILL.md”---name: forecastdescription: 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 theydescribed, or numbers they typed) into a short, practical answer. This skilldoes 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 theunits the user used. If the data is incomplete, say what is missing ratherthan guessing.What to look at:
descriptionis 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-descriptionmatters 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
/forecastor the model decides the skill is relevant. It reads like instructions to a colleague, not like a prompt template.
commands/forecast-brief.md
Section titled “commands/forecast-brief.md”---description: Write a two-line forecast brief for a placeargument-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:
descriptionandargument-hintdrive the/palette in the terminal UI.- Every literal
$ARGUMENTSin 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.
hooks/log-tool-use.sh
Section titled “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 0mkdir -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 0What to look at:
- The script reads the whole payload from stdin once. For
PostToolUsethe payload carriestool_name,tool_inputandtool_responseamong other keys; the script uses onlytool_name. MUSE_PLUGIN_DATA_DIRis a writable directory kept per plugin, but it may not exist yet. Themkdir -pline is what makes the first write succeed.- The script always exits 0. On
PostToolUsean 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
HOMEandPATH, so the script relies on nothing else.
Hook events and payloads lists every event and the keys each one delivers.
How it was verified
Section titled “How it was verified”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:
muse skills validate weather/skills/forecastmuse plugins validate weathervalid forecastvalid weather native skills=1 commands=1 hooks=1 mcp=0 reminders=0 diagnostics=0Install and inspect:
muse plugins install weathermuse plugins inspect weatherinstalled weather 0.1.0 enabled=true trust=user-local provenance=native-local cache=<data dir>/plugins/cache/local/weather/bacc26ed…/packagewarning third-party plugin: hooks require review before activation; skills and commands are active without review while the plugin is enabledweather 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…/packagewarning third-party plugin: hooks require review before activation; skills and commands are active without review while the plugin is enabledruntime-capability plugin:weather:hook:log-tool-use status=review_neededAt this point /forecast and /forecast-brief already work in a session. The
hook is waiting for review. Approve it and inspect again:
muse plugins approve weathermuse plugins inspect weatherapprove plugin:weather:hook:log-tool-useweather 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…/packagewarning third-party plugin: hooks require review before activation; skills and commands are active without review while the plugin is enabledruntime-capability plugin:weather:hook:log-tool-use status=trusted_enabledThe 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:
{ "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" }}muse plugins hook test weather:log-tool-use --fixture post-tool-use.jsoncat "<data dir>/plugins/data/weather/tool-use.log"hook-test weather:log-tool-use status=completed2026-09-17T04:32:25Z read_filestatus=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.
Remove it
Section titled “Remove it”muse plugins remove weather --delete-dataremoved 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.
Next steps
Section titled “Next steps”- A guard hook that blocks risky tool calls shows the other
side of hooks: a
PreToolUsehook that stops a tool call. - Quickstart: your first plugin builds this plugin from an empty directory and covers the mistakes you are most likely to make.
- Hooks explains how hook commands are run, what they receive and how they answer.
- Plugins in SDK sessions shows how
the
forecastskill appears to a client ofmuse serve.