diff --git a/skills/datastore-nutrition-logging/SKILL.md b/skills/datastore-nutrition-logging/SKILL.md new file mode 100644 index 0000000..d5477d5 --- /dev/null +++ b/skills/datastore-nutrition-logging/SKILL.md @@ -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 --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='' AND start >= ''"`. + 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//YYYY-MM.jsonl`, remove the now-empty `/` 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. diff --git a/skills/heartbeat-monitor-rules/SKILL.md b/skills/heartbeat-monitor-rules/SKILL.md new file mode 100644 index 0000000..5220c00 --- /dev/null +++ b/skills/heartbeat-monitor-rules/SKILL.md @@ -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. diff --git a/skills/mcp-server-debugging/SKILL.md b/skills/mcp-server-debugging/SKILL.md new file mode 100644 index 0000000..643f393 --- /dev/null +++ b/skills/mcp-server-debugging/SKILL.md @@ -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 `__*` 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 < /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" `, 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()/.`) → `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 ` 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 `__*` 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}" ]`). diff --git a/skills/memory-wiki-synthesis/SKILL.md b/skills/memory-wiki-synthesis/SKILL.md new file mode 100644 index 0000000..f6937a7 --- /dev/null +++ b/skills/memory-wiki-synthesis/SKILL.md @@ -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-.md` with frontmatter `type: Source`, a unique `id: `, `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: [""]` (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. diff --git a/skills/pdf-onepager/SKILL.md b/skills/pdf-onepager/SKILL.md new file mode 100644 index 0000000..5edb1b1 --- /dev/null +++ b/skills/pdf-onepager/SKILL.md @@ -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 `), then print per the brother-printer skill: `uv run /home/node/mop-skills/mop-skills/skills/brother-printer/scripts/brother_printer.py print --file --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. diff --git a/skills/sonos-media-playback/SKILL.md b/skills/sonos-media-playback/SKILL.md new file mode 100644 index 0000000..b1ed449 --- /dev/null +++ b/skills/sonos-media-playback/SKILL.md @@ -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.