feat(skills): promote untracked pod skills into mop-skills

This commit is contained in:
Moritz Graf
2026-10-16 21:10:26 +02:00
parent 4f72e4d71b
commit 8bdd06e598
6 changed files with 129 additions and 0 deletions
@@ -0,0 +1,22 @@
---
name: "datastore-nutrition-logging"
description: "Log nutrition intake via datastore CLI; verify kind and repair mis-parsed events."
---
# Datastore Nutrition Logging
Log consumed food to the datastore CLI and repair events that landed under the wrong kind.
## Steps
1. Log an item: run
`uv run /home/node/mop-skills/mop-skills/skills/datastore/scripts/datastore.py add nutrition.intake --subject <person> --actor agent:koch --payload '{"item": "...", "qty": 250, "unit": "g", "protein_g": 7.5, "carbs_g": 87.5, "fat_g": 27.5, "kcal": 625.0}' --estimated`
Pass `nutrition.intake` as the positional argument and `--estimated` as a bare flag with no value. Done when the response contains `feedback.day_total_formatted`.
2. Verify the kind: run
`... datastore.py query "SELECT id, kind, start, payload FROM events WHERE subject='<person>' AND start >= '<YYYY-MM-DD>'"`.
Done when the new event shows `kind = "nutrition.intake"`. If it shows another kind (e.g. `"true"`), a flag value was parsed as the positional kind.
3. Repair a mis-kinded event: delete its line from `/home/node/.openclaw/wiki/main/data/events/<wrong-kind>/YYYY-MM.jsonl`, remove the now-empty `<wrong-kind>/` directory, then re-run step 1. Done when step 2 passes.
4. Refresh the read-only views: run
`PYTHONPATH=/home/node/mop-skills/mop-skills/skills/datastore/scripts uv run /home/node/mop-skills/mop-skills/skills/datastore/scripts/render_views.py`.
Done when it prints the rendered nutrition-log paths.
5. Report the day total from `feedback.day_total_formatted`; never compute macros by hand.
+15
View File
@@ -0,0 +1,15 @@
---
name: "heartbeat-monitor-rules"
description: "Store heartbeat monitor rules as a skill for session handling"
---
# Heartbeat monitor rules
When a heartbeat monitor scratch context is provided:
1. Follow the scratch context as the source of truth for what to do in that turn.
2. Do not create or change recurring tasks from heartbeat messages. Use the automations tool for recurring tasks.
3. Do not infer or repeat old tasks from previous chats.
4. If the heartbeat has nothing that needs attention, respond exactly: NO_REPLY.
This skill documents the handling rules for heartbeat messages and is triggered when a heartbeat scratch context is received.
+32
View File
@@ -0,0 +1,32 @@
---
name: "mcp-server-debugging"
description: "MCP server tools missing or fails to start (\"Connection closed\"): reproduce via stdio client, pin mcp&lt;2, fix writable HOME/PATH."
---
# MCP Server Debugging
Diagnose and fix OpenClaw MCP servers whose tools never appear or that crash on startup.
## Trigger
- A configured MCP server's tools are absent from the session, or a stdio MCP client fails `initialize` with `Connection closed`.
- A newly added MCP server does not show up as `<name>__*` tools.
## Steps
1. Read the server definition: call `gateway` with `action=config.get`, `path=mcp`; note `command`, `args`, `env`, `cwd` for the failing server.
2. Reproduce and capture the real error: run the configured command directly with stdin from `/dev/null` (e.g. `timeout 60 python3 <launcher> < /dev/null 2>&1 | head -40`). Completion: stderr shows the underlying failure, not only the client-side "Connection closed".
3. Map the error to the fix:
- `ModuleNotFoundError: No module named 'mcp.server.fastmcp'` → the runner pulled **mcp 2.x**, where `FastMCP` was renamed to `MCPServer`. Fix: pin 1.x, e.g. run `uvx --with "mcp<2" <package>`, or add the pin inside the launcher's `os.execvp` call.
- `Read-only file system` on a home/credentials path (launcher writes to `Path.home()/.<name>`) → `HOME` is read-only. Fix: point `HOME` to a writable dir (e.g. `/tmp`) or change the written path.
- `FileNotFoundError: uvx` → prepend the uvx directory (normally `/home/openclaw/.openclaw/bin`) to `PATH`.
4. Verify end-to-end with a minimal stdio client before declaring success: spawn the fixed command, `initialize`, `list_tools`, then call the target tool and confirm real data returns. Use `uv run --with mcp --python 3.12 python <client.py>` where the client uses `mcp.client.stdio.stdio_client` + `ClientSession`, passing `env` with the corrected `HOME`/`PATH`.
5. Route config changes through the `openclaw` tool (human approval required); never edit gateway config files directly. Report the exact old → new command.
## Tool calls fail with argument validation errors
- An MCP tool returning a pydantic `Unexpected keyword argument` error means the argument names are wrong for that server, not that the server is down: read the real schema with `tool_describe` and retry with its exact parameters.
- Home Assistant MCP: `ha_call_service` takes `{domain, service, entity_id, data}` (not `service_data`/`target`); `ha_search_entities` takes `{query}` only (no `domain`). A successful call reports `success: true` in `structuredContent`.
- A type/filter variant that errors with `'NoneType' object has no attribute 'get'` (e.g. Spotify MCP `types=playlist`) → switch to a working type (`types=track` or `album`), act on that URI, and report the server bug instead of retrying the same call.
## Pitfalls
- A session only exposes tools loaded at session start; a newly added server's tools appear as `<name>__*` only after a gateway reload.
- Test with the same `HOME`/`PATH` the gateway uses, otherwise you validate the wrong environment.
- Never print secret values; check presence only (`[ -n "${VAR}" ]`).
+13
View File
@@ -0,0 +1,13 @@
---
name: "memory-wiki-synthesis"
description: "wiki_apply create_synthesis fails without sourceIds; create a source page first, then file the synthesis"
---
# Filing a Memory-Wiki Synthesis
Use when filing a research dossier or synthesis into the memory wiki via `wiki_apply` with `op: "create_synthesis"`.
1. Call `wiki_apply` with `op: "create_synthesis"`, `title`, and `body`. If it returns `wiki mutation requires at least one sourceId for create_synthesis`, do not retry blind. Completion: the error names the missing `sourceIds`.
2. Create a source page first. Read the vault path from `wiki_status` (`vaultPath`, e.g. `/home/openclaw/.openclaw/wiki/main`), then write `sources/YYYY-MM-DD-<slug>.md` with frontmatter `type: Source`, a unique `id: <slug>`, `title`, and a `sources:` list of the URLs used. Completion: the file exists and carries an `id`.
3. Re-run `wiki_apply` `op: "create_synthesis"` passing `sourceIds: ["<slug>"]` (the source page `id`) plus `title`, `body`, and any `questions`/`confidence`. Completion: the response reports the new `syntheses/...` page path and refreshes the index files.
4. Run `wiki_lint` after the update. Completion: lint returns; expect a large pre-existing baseline of structural warnings, so only act on issues for the pages just written.
+24
View File
@@ -0,0 +1,24 @@
---
name: "pdf-onepager"
description: "Generate styled one-page PDFs in the sandbox via fpdf2 and print with brother-printer."
---
# PDF One-Pager (fpdf2)
Generate a styled, single-page PDF in the sandbox, then print it with the brother-printer skill.
## Steps
1. Draft the content as a Python script using `fpdf2` (FPDF, A4, `set_auto_page_break(False)`, margins ~15mm). Build layout with helper functions: colored title, section headers with rule line, label+text bullets, a filled warning box (`set_fill_color` + `multi_cell(fill=True)`), and a small gray source footer. Do not use emoji/HTML-entities in text: fpdf2 core fonts (helvetica) support only Latin-1 — replace emoji with plain words.
- Done when: the script is saved (e.g. `/tmp/make_pdf.py`) with an absolute output path and prints a pages count from `pdf.pages_count`.
2. Run `uv run --with fpdf2 /tmp/make_pdf.py`.
- Do NOT attempt `weasyprint` (fails in this sandbox: missing system libs) or wkhtmltopdf/chromium/pandoc (not installed). Go straight to fpdf2.
- Done when: script prints `done, pages: 1` (or expected count) and the PDF file exists.
3. Verify the file (`ls -la <path>`), then print per the brother-printer skill: `uv run /home/node/mop-skills/mop-skills/skills/brother-printer/scripts/brother_printer.py print --file <pdf> --title <title> --color monochrome --nup 1 --duplex two-sided-long-edge --chat-id <chat_id>`.
- For print-option selection, use `ask_user` with the standard preset menu from the brother-printer skill.
- Done when: script reports 'Druck erfolgreich abgeschlossen'.
## Reference
- fpdf2 API: `pdf.cell(w, h, text, new_x="LMARGIN", new_y="NEXT")` for labels/headers; `pdf.multi_cell(W, h, text, new_x="LMARGIN", new_y="NEXT")` for wrapped text; colors via `set_text_color/set_fill_color/set_draw_color(RGB tuples)`.
- Keep single-page fit: font 9–9.5pt body, 11–11.5pt headers, 17pt title; if content overflows, trim bullets rather than shrinking below 9pt.
- Save the PDF in the workspace (e.g. `/home/openclaw/.openclaw/workspace/<Name>.pdf`) so it can be resent later.
+23
View File
@@ -0,0 +1,23 @@
---
name: "sonos-media-playback"
description: "Play music on Sonos speakers via Home Assistant; when Spotify search or play_media fails, use Sonos source_list favorites."
---
# Sonos Media Playback
Play music (genre, playlist, artist, "50% volume") on a room Sonos via Home Assistant MCP.
## Steps
1. Find the room player: `homeassistant__ha_search_entities` with `domain_filter="media_player"` and the room name (German room names match, e.g. "bad" -> `media_player.bad`). Done when one media_player entity is found.
2. Set volume first with `ha_call_service(domain="media_player", service="volume_set", data={"volume_level": <0-1>})` — this works even while the player is paused/idle. Percent requested / 100.
3. If the player is `paused` or `idle`, wake it with `media_player.media_play` so Spotify Connect may register it.
4. Resolve the requested music. Prefer, in order:
- `spotify__search` for playlist/track URIs, then play via the Spotify MCP `play` tool. If `play` returns 404 "No active device found": call `spotify__get_devices`, pick the room-matching device, and retry `play` with that `device_id` explicitly.
- If Spotify search errors or no Spotify device is active: get the player state with `ha_get_state` and read its `source_list` (Sonos favorites). Pick the favorite closest to the requested genre (e.g. dark techno -> `ChilloutTechno`, sets -> `Fusion 2026 Sets`), then `ha_call_service(domain="media_player", service="select_source", data={"source": "<favorite>"})`.
5. Verify: `ha_get_state` on the entity shows `playing` with `media_title`/`media_playlist` and the requested `volume_level`. Report title and volume to the user; offer the nearest alternative favorite if the genre match is loose.
## Do not retry
- `ha_call_service(media_player, play_media)` with any `spotify:` URI on Sonos: returns HTTP 500 regardless of `media_content_type` (`music`/`playlist`) — skip it and go straight to `source_list` favorites.
- Repeated retries of a failing `spotify__search` with different arguments: the failure is server-side; fall back once, then use favorites.