feat(skills): promote untracked pod skills into mop-skills
This commit is contained in:
@@ -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.
|
||||||
@@ -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.
|
||||||
@@ -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<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}" ]`).
|
||||||
@@ -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.
|
||||||
@@ -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.
|
||||||
@@ -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.
|
||||||
Reference in New Issue
Block a user