Skill: igornumeriano/openclaw-handbook (version 1.6.2)

--- name: OpenClaw Documentation Expert — Live Fetch description: Fetches live from docs.openclaw.ai on every question. Whole-docs index, working scripts (not echo placeholders), CI-style selftest for decision-tree rot, worked routing examples, SOUL/multi-agent/sessions/platforms/nodes/clawhub covered. version: 1.6.2 metadata: openclaw: requires: bins: - curl emoji: "🦞" homepage: https://docs.openclaw.ai --- # OpenClaw Documentation Expert — Live Fetch ## Activation Activate when the user asks about OpenClaw: configuration, concepts, channels, CLI, troubleshooting, multi-agent, SOUL.md, AGENTS.md, memory, sessions, automation, gateway, providers, skills, plugins, install. Do NOT activate for: general coding questions, non-OpenClaw tools (Claude Code, Codex, OpenCode), or meta-questions about this skill. ## Out-of-Scope Response If a question is partially about OpenClaw and partially about another tool, answer only the OpenClaw portion and say `not covered here` for the rest. Never extrapolate OpenClaw docs to explain other tools. ## Behavior Contract For every in-scope question, execute in order: 1. Route the question through the **Decision Tree** below → pick ONE page 2. Fetch that page: `scripts/fetch.sh <path>` (or `curl -sfL https://docs.openclaw.ai/<path>.md`) 3. Answer using only content from the fetched page 4. Cite the source URL at the end of the answer 5. If the fetched page does not contain the answer, fetch at most ONE additional page before falling back to `scripts/search.sh <keyword>` 6. If still not found, respond: `Not documented at docs.openclaw.ai as of <timestamp from verify.sh>` 7. If you had to fall back to `find.sh` or `search.sh`, or if the user corrects your routing, log the miss: `scripts/record_miss.sh "<user question>" "<path you used>"` **Never answer from memory alone.** Never fabricate a URL path. Never combine memory with fetched content without marking which is which. ## Three-Tier Fetch Strategy | Tier | Endpoint | When to use | |---|---|---| | 1 | `llms.txt` | Discovery — "what page exists for X?" → `scripts/find.sh <keyword>` | | 2 | `<page>.md` | Specific answer — one page, cited → `scripts/fetch.sh <path>` | | 3 | `llms-full.txt` | Cross-section search — last resort → `scripts/search.sh <keyword>` | Always prefer Tier 2. Use Tier 1 only when Decision Tree is ambiguous. Use Tier 3 only when a targeted fetch misses. ## Scripts All scripts are in `scripts/` and require only `curl` (POSIX userspace: `awk`, `sed`, `grep`, `xargs`). - **`find.sh <keyword>`** — list pages whose title **or path** matches the keyword (tab-separated: title, path). Slug-only terms like `qmd`, `honcho` match. Keyword is matched as a fixed string (grep -F), so `+`, `?`, `|`, `{` are safe. - **`fetch.sh <path>`** — fetch one page; accepts paths with/without leading slash. Preserves extensions `.md`, `.json`, `.txt`, `.yaml`, `.yml`; assumes `.md` otherwise. Examples: `concepts/soul`, `concepts/soul.md`, `/channels/telegram.md`, `api-reference/openapi.json`. - **`search.sh <keyword> [ctx]`** — literal grep (grep -F) across full docs with N lines of context (default 10), capped at 200 lines. Caches `llms-full.txt` for 1h at `$TMPDIR/openclaw-llms-full.txt`. - **`verify.sh`** — print page count + sha256 + server `Last-Modified` + local fetch timestamp (proof of liveness from both sides) - **`selftest.sh`** — HEAD-check every doc path referenced in SKILL.md and EXAMPLES.md, in parallel (8 workers; override with `PARALLEL=N`). Classifies STALE (3xx → 200, path renamed) separately from MISSING (404/5xx, page gone). Also runs a `fetch.sh` smoke test against a non-`.md` path so extension regressions are caught. Exits non-zero on any failure. - **`record_miss.sh <question> <path>`** — append a routing miss to `~/.openclaw/openclaw-docs/misses.md`. Never edits SKILL.md. - **`record_miss.sh --tally [N]`** — group recorded misses by fetched path, show top N counts. Use this to spot when a path crosses the 3-hit promotion bar. These are real working bash scripts, not echo placeholders. Run `scripts/verify.sh` at session start to confirm docs are current. Run `scripts/selftest.sh` before editing the Decision Tree or publishing a new version. ## Decision Tree All paths below are validated against `llms.txt`. Route first, fetch second. ``` IDENTITY / PERSONALITY SOUL.md, tone, agent voice → concepts/soul.md MULTI-AGENT Multiple agents, workspaces → concepts/multi-agent.md Delegate architecture → concepts/delegate-architecture.md AGENT LOOP / RUNTIME How the agent loop works → concepts/agent-loop.md Agent runtime → concepts/agent.md Agent workspace (AGENTS.md) → concepts/agent-workspace.md System prompt structure → concepts/system-prompt.md MEMORY General → concepts/memory.md Built-in → concepts/memory-builtin.md QMD (vector search) → concepts/memory-qmd.md Honcho (external) → concepts/memory-honcho.md Search memory → concepts/memory-search.md Active memory → concepts/active-memory.md Dreaming / consolidation → concepts/dreaming.md SESSIONS Model, history → concepts/session.md Pruning → concepts/session-pruning.md Session tool → concepts/session-tool.md Context / compaction → concepts/context.md, concepts/compaction.md GATEWAY Sandboxing → gateway/sandboxing.md Logging → gateway/logging.md Network model → gateway/network-model.md Security → gateway/security/index.md AUTOMATION Cron jobs → automation/cron-jobs.md Hooks → automation/hooks.md Standing orders → automation/standing-orders.md Task flow → automation/taskflow.md Background tasks → automation/tasks.md CHANNELS Telegram → channels/telegram.md Discord → channels/discord.md WhatsApp → channels/whatsapp.md Slack → channels/slack.md iMessage → channels/imessage.md iMessage (migrating from BlueBubbles) → channels/imessage-from-bluebubbles.md Signal → channels/signal.md Matrix → channels/matrix.md Microsoft Teams → channels/msteams.md Other (IRC, LINE, Nostr, ...) → channels/<name>.md Channel routing → channels/channel-routing.md Pairing / allowlist → channels/pairing.md Groups → channels/groups.md Troubleshooting → channels/troubleshooting.md TOOLS / SKILLS Skills (create, install, share) → tools/skills.md All tools overview → tools/index.md PROVIDERS (AI models) Any provider → providers/<name>.md CLI Any command → cli/<command>.md Full reference → cli/index.md INSTALL / SETUP Getting started → start/getting-started.md Bootstrapping → start/bootstrapping.md Raspberry Pi → install/raspberry-pi.md Ansible → install/ansible.md VPS → vps.md Pi → pi.md PLUGINS Architecture → plugins/architecture.md Building → plugins/building-plugins.md Bundles → plugins/bundles.md PLATFORMS (apps) Overview → platforms/index.md iOS app → platforms/ios.md Android app → platforms/android.md Linux app → platforms/linux.md macOS bundled gateway → platforms/mac/bundled-gateway.md macOS Canvas → platforms/mac/canvas.md NODES (media I/O) Overview → nodes/index.md Audio / voice notes → nodes/audio.md Images → nodes/images.md Camera capture → nodes/camera.md Media understanding → nodes/media-understanding.md WEB (UIs) Overview → web/index.md Dashboard → web/dashboard.md WebChat → web/webchat.md TUI → web/tui.md Control UI → web/control-ui.md CLAWHUB (marketplace) Overview → clawhub/index.md Publishing skills → clawhub/publishing.md ClawHub CLI → clawhub/cli.md HTTP API → clawhub/http-api.md Skill format → clawhub/skill-format.md HELP / FAQ General FAQ → help/faq.md First-run setup → help/faq-first-run.md Models and auth → help/faq-models.md Environment variables → help/environment.md Debugging walkthrough → help/debugging.md REFERENCE Default AGENTS.md → reference/AGENTS.default.md API usage and costs → reference/api-usage-costs.md Code mode → reference/code-mode.md Release policy → reference/RELEASING.md ANNOUNCEMENTS BlueBubbles removal → announcements/bluebubbles-imessage.md DEBUGGING Diagnostics flags → diagnostics/flags.md Logging (root) → logging.md Network (root) → network.md Node + tsx crash → debug/node-issue.md SECURITY / AUTH Auth credential semantics → auth-credential-semantics.md OAuth → concepts/oauth.md Threat model → security/THREAT-MODEL-ATLAS.md WEB / API OpenAPI spec → api-reference/openapi.json ``` If no branch matches: `scripts/find.sh <keyword>` → pick the top result → fetch. ## Synonym Map Common user phrasings → canonical decision-tree branch. Use this before falling back to `find.sh`. | User says | Route to | |---|---| | "personality", "tone of voice", "persona", "style" | IDENTITY → `concepts/soul.md` | | "operational manual", "workspace rules", "AGENTS.md" | `concepts/agent-workspace.md` | | "context window", "context limit", "too long" | `concepts/context.md` + `concepts/compaction.md` | | "history", "chat log", "conversation memory" | SESSIONS → `concepts/session.md` | | "vector search", "embeddings memory", "semantic recall" | `concepts/memory-qmd.md` | | "background consolidation", "sleep cycle", "memory sync" | `concepts/dreaming.md` | | "scheduled task", "recurring job", "crontab" | `automation/cron-jobs.md` | | "pre/post-tool hook", "lifecycle event" | `automation/hooks.md` | | "always-on instructions", "persistent rules" | `automation/standing-orders.md` | | "bot", "messenger", "chat integration" | CHANNELS → `channels/<name>.md` | | "jail", "isolation", "sandbox" | `gateway/sandboxing.md` | | "firewall", "egress rules", "allowlist" | `gateway/network-model.md` | | "skill", "plugin package", "tool bundle" | `tools/skills.md` | | "LLM provider", "model backend", "Anthropic/OpenAI/etc" | `providers/<name>.md` | | "publish skill", "ship to marketplace", "ClawHub" | CLAWHUB → `clawhub/publishing.md` | | "skill manifest", "SKILL.md format", "frontmatter" | `clawhub/skill-format.md` | | "mobile app", "iPhone", "iOS app", "Android app" | PLATFORMS → `platforms/ios.md` / `platforms/android.md` | | "desktop app", "Mac app", "Linux app" | PLATFORMS → `platforms/linux.md` / `platforms/mac/bundled-gateway.md` | | "voice note", "audio message", "speech-to-text" | NODES → `nodes/audio.md` | | "send image", "photo", "screenshot in chat" | NODES → `nodes/images.md` | | "camera", "take a picture" | NODES → `nodes/camera.md` | | "vision", "OCR", "describe image" | NODES → `nodes/media-understanding.md` | | "web dashboard", "browser UI" | WEB → `web/dashboard.md` | | "WebChat", "embed chat", "chat widget" | `web/webchat.md` | | "terminal UI", "TUI" | `web/tui.md` | | "env var", "ENV", "configuration variables" | `help/environment.md` | | "first-time setup", "getting started", "newbie" | `help/faq-first-run.md` | | "which model", "model selection", "auth model" | `help/faq-models.md` | | "default AGENTS.md", "out-of-box config", "stock workspace" | `reference/AGENTS.default.md` | | "how much will it cost", "API pricing", "token cost" | `reference/api-usage-costs.md` | | "code mode", "execute code", "code execution" | `reference/code-mode.md` | | "BlueBubbles is gone", "iMessage broke", "imsg path" | `announcements/bluebubbles-imessage.md` + `channels/imessage.md` | | "node tsx crash", "tsx node error" | `debug/node-issue.md` | ## Worked Examples See `EXAMPLES.md` for ~13 canonical question → routing → fetch → citation cases (few-shot anchors covering each major branch plus one out-of-scope negative example). ## Evolution Loop This skill improves from **real friction only**, not from speculation. The loop: 1. **Detect** — you fall back to `find.sh`/`search.sh`, or the user corrects your routing. 2. **Record** — run `scripts/record_miss.sh "<question>" "<path>"`. Log lives at `~/.openclaw/openclaw-docs/misses.md`. 3. **Review** — periodically the maintainer reads the misses log. 4. **Promote (human decision)** — a synonym/new route enters the Decision Tree or Synonym Map only after **three comparable hits** on the same missed term. One-off questions stay in the log. 5. **Validate** — any edit to routes must pass `scripts/selftest.sh` before commit. Rules for evolution: - **Start from friction, not novelty.** No new synonyms "just in case." - **One lever per change.** Don't touch three branches in the same PR. - **Evidence, not gut feel.** The misses log is the evidence; three comparable entries is the bar. - **Prefer promotion over rewrite.** Add a synonym row; don't restructure the tree. ## Boundaries (Self-Modification) This skill **never** modifies its own installed files at runtime: - Do not edit `SKILL.md`, `EXAMPLES.md`, or any file under `scripts/` from within an agent answer. - Do not invent new decision-tree branches on the fly — use the current tree, then log the miss. - Do not write to `~/.openclaw/openclaw-docs/` anything other than the misses log (one append per miss). - Do not exfiltrate user messages beyond the single flattened question passed to `record_miss.sh`. Promotions from the misses log into SKILL.md are a **human commit**, reviewed, run through `selftest.sh`, and published as a new version. The agent proposes; the maintainer disposes. ## Anti-Patterns (Testable Don'ts) Common mistakes when answering OpenClaw questions. Each is a rule that can be checked against a real answer. - **DON'T** say SOUL.md and AGENTS.md are the same. SOUL = personality. AGENTS.md = operational manual. - **DON'T** claim `openclaw cron add` defaults to a persistent session. Default is **`isolated`** (one-shot). - **DON'T** recommend sharing `agentDir` between agents. It causes auth and session collisions. - **DON'T** reference `docs.clawd.bot` or `clawdbot` CLI. Renamed to OpenClaw. Docs live at `docs.openclaw.ai`. - **DON'T** fetch a page without `.md` extension. May return HTML instead of markdown. - **DON'T** invent section URLs. If a path isn't in `llms.txt`, it doesn't exist. - **DON'T** answer "I think..." — fetch or say not documented. ## Key Concepts (Verified) Quick reference. Always back up with a Tier 2 fetch for critical answers. **SOUL.md** — system-prompt layer for tone/opinions/brevity. Keep short. `concepts/soul.md` **AGENTS.md** — operational manual (tools, workspace, channels). Read alongside SOUL. `concepts/agent-workspace.md` **Multi-agent** — each agent has its own workspace + `agentDir` + sessions. Sessions keyed `agent:<id>:<key>`. Default `agentId = main`. `concepts/multi-agent.md` **Session** — JSONL history on disk, survives restarts. Recall via `sessions_history` tool (sanitized). `concepts/session.md` **Memory** — active (in-context) / QMD (vector) / dreaming (async consolidation) / honcho (external). `concepts/memory.md` **Cron** — `openclaw cron add --name X --cron "*/15 * * * *" --session isolated --message "..."`. Default session = isolated. `automation/cron-jobs.md` **Skills** — live at `~/.openclaw/skills/<name>/SKILL.md`. Baseline via `agents.defaults.skills`, per-agent via `agents.list[].skills`. `tools/skills.md` **ClawHub** — public marketplace for skills/plugins. Install via `openclaw skills install <slug>`, publish via `clawhub publish`. `clawhub/index.md` + `clawhub/publishing.md` **Nodes (media)** — handlers for audio, image, camera, media understanding. Cross-channel; not tied to a single messenger. `nodes/index.md` **Platforms (apps)** — native iOS / Android / Linux / macOS clients that talk to a gateway. Pairing model lives in `channels/pairing.md`. `platforms/index.md` --- > Replacement for outdated clawddocs. Fetches live, never stale, no echo placeholders.

path: EXAMPLES.md
content: # Worked Routing Examples

Canonical question → decision tree branch → fetch → citation.
These are few-shot anchors, not answers. Always run `scripts/fetch.sh <path>` before replying.

---

**Q: "How do I write a good SOUL.md?"**
Branch: IDENTITY → `concepts/soul.md`
Fetch: `scripts/fetch.sh concepts/soul`
Cite: `https://docs.openclaw.ai/concepts/soul.md`
Anti-pattern check: don't conflate with AGENTS.md.

---

**Q: "Can two agents share the same agentDir?"**
Branch: MULTI-AGENT → `concepts/multi-agent.md`
Fetch: `scripts/fetch.sh concepts/multi-agent`
Cite: `https://docs.openclaw.ai/concepts/multi-agent.md`
Anti-pattern check: sharing agentDir → auth/session collisions.

---

**Q: "Does `openclaw cron add` keep a persistent session by default?"**
Branch: AUTOMATION → `automation/cron-jobs.md`
Fetch: `scripts/fetch.sh automation/cron-jobs`
Cite: `https://docs.openclaw.ai/automation/cron-jobs.md`
Anti-pattern check: default is `isolated`, not persistent.

---

**Q: "How do I set up Telegram as a channel?"**
Branch: CHANNELS → `channels/telegram.md`
Fetch: `scripts/fetch.sh channels/telegram`
Cite: `https://docs.openclaw.ai/channels/telegram.md`

---

**Q: "How does vector memory (QMD) differ from active memory?"**
Branch: MEMORY → `concepts/memory-qmd.md` (primary), `concepts/active-memory.md` (comparison)
Fetch two pages (one extra allowed by Behavior Contract step 5).
Cite both URLs.

---

**Q: "What is dreaming in OpenClaw?"**
Branch: MEMORY → `concepts/dreaming.md`
Fetch: `scripts/fetch.sh concepts/dreaming`
Cite: `https://docs.openclaw.ai/concepts/dreaming.md`

---

**Q: "How does session pruning work and when does it trigger?"**
Branch: SESSIONS → `concepts/session-pruning.md`
Fetch: `scripts/fetch.sh concepts/session-pruning`
Cite: `https://docs.openclaw.ai/concepts/session-pruning.md`

---

**Q: "How do I install a skill from clawhub?"**
Branch: TOOLS / SKILLS → `tools/skills.md`
Fetch: `scripts/fetch.sh tools/skills`
Cite: `https://docs.openclaw.ai/tools/skills.md`

---

**Q: "Which AI providers does OpenClaw support?"**
Branch: PROVIDERS — ambiguous specific, start with discovery.
Run: `scripts/find.sh provider` → pick top result → fetch.
Cite fetched URL.

---

**Q: "How is the gateway sandbox network configured?"**
Branch: GATEWAY → `gateway/sandboxing.md` (primary), `gateway/network-model.md` (related)
Fetch primary; fetch secondary only if primary doesn't cover the question.

---

**Q: "My Telegram bot isn't responding — where do I start?"**
Branch: CHANNELS → `channels/troubleshooting.md`
Fetch: `scripts/fetch.sh channels/troubleshooting`
Cite: `https://docs.openclaw.ai/channels/troubleshooting.md`

---

**Q: "Can I run OpenClaw on a Raspberry Pi?"**
Branch: INSTALL — two candidates: `install/raspberry-pi.md` and `pi.md`.
Default to `install/raspberry-pi.md` (more specific). If it 404s or lacks content, fall back to `pi.md`.

---

**Q: "Is there an OpenAPI spec for the HTTP API?"**
Branch: WEB / API → `api-reference/openapi.json`
Fetch: `scripts/fetch.sh api-reference/openapi.json`
Cite: `https://docs.openclaw.ai/api-reference/openapi.json`

---

**Q: "How do I install the iOS app and pair it with my gateway?"**
Branch: PLATFORMS → `platforms/ios.md`
Fetch: `scripts/fetch.sh platforms/ios.md`
Cite: `https://docs.openclaw.ai/platforms/ios.md`

---

**Q: "How do I publish a skill to ClawHub?"**
Branch: CLAWHUB → `clawhub/publishing.md` (primary). If question is about the manifest fields, fetch `clawhub/skill-format.md` instead.
Fetch: `scripts/fetch.sh clawhub/publishing`
Cite: `https://docs.openclaw.ai/clawhub/publishing.md`

---

**Q: "Which environment variables does OpenClaw read at startup?"**
Branch: HELP / FAQ → `help/environment.md`
Fetch: `scripts/fetch.sh help/environment`
Cite: `https://docs.openclaw.ai/help/environment.md`

---

**Q: "Can I send a voice note from WhatsApp and have the agent transcribe it?"**
Branch: NODES → `nodes/audio.md` (primary), `nodes/media-understanding.md` (transcription/understanding context).
Fetch primary first; fetch secondary only if the answer is incomplete.
Cite the URL(s) actually used.

---

**Q: "Why did BlueBubbles support get removed and what do I do instead?"**
Branch: ANNOUNCEMENTS → `announcements/bluebubbles-imessage.md` (context), then CHANNELS → `channels/imessage.md` (new path).
Fetch both; this is an explicit two-page case.
Cite both URLs.

---

**Q: "Where can I see what the default AGENTS.md ships with?"**
Branch: REFERENCE → `reference/AGENTS.default.md`
Fetch: `scripts/fetch.sh reference/AGENTS.default`
Cite: `https://docs.openclaw.ai/reference/AGENTS.default.md`

---

**Negative example — out of scope**

Q: "How do I configure hooks in Claude Code?"
Response: "Claude Code hooks are not covered here. For OpenClaw automation hooks see `automation/hooks.md`."
Do NOT extrapolate OpenClaw docs to explain Claude Code.


path: scripts/fetch.sh
content: #!/usr/bin/env bash
# Fetch a single OpenClaw doc page.
# Usage: fetch.sh <path>
# Accepts with/without leading slash. If no extension is given, .md is assumed.
# Recognized extensions are preserved (.md, .json, .txt, .yaml, .yml).
# Examples:
#   fetch.sh concepts/soul
#   fetch.sh concepts/soul.md
#   fetch.sh /channels/telegram.md
#   fetch.sh api-reference/openapi.json
set -eu
[ $# -lt 1 ] && { echo "usage: $0 <path>" >&2; exit 2; }
path="$1"
path="${path#/}"
case "$path" in
  *.md|*.json|*.txt|*.yaml|*.yml) ;;
  *) path="${path}.md" ;;
esac
curl -sfL "https://docs.openclaw.ai/${path}"


path: scripts/find.sh
content: #!/usr/bin/env bash
# Find OpenClaw doc pages matching a keyword (case-insensitive, literal).
# Usage: find.sh <keyword>
# Matches on either the page TITLE or the URL PATH so terms that live only in
# the slug (e.g. "qmd", "honcho") are still discoverable.
# Keyword is matched as a fixed string (grep -F), so regex metacharacters and
# weird chars like '+', '?', '|', '{' are safe.
# Output: one match per line, format "Title<TAB>path.md"
set -euo pipefail
[ $# -lt 1 ] && { echo "usage: $0 <keyword>" >&2; exit 2; }
kw="$1"
curl -sfL https://docs.openclaw.ai/llms.txt \
  | sed -nE 's|^- \[(.*)\]\(https://docs\.openclaw\.ai/(.*)\)$|\1\t\2|p' \
  | grep -iF -- "$kw" || true


path: scripts/record_miss.sh
content: #!/usr/bin/env bash
# Record a routing miss (or tally recorded misses) for synonym-map promotion.
#
# Record mode:
#   record_miss.sh "<user question>" "<path you ended up fetching>"
#   Appends one line to ~/.openclaw/openclaw-docs/misses.md.
#
# Tally mode:
#   record_miss.sh --tally [N]
#   Groups recorded misses by fetched path and shows counts (top N, default 20).
#   Use this to spot which paths cleared the "3 comparable hits" promotion bar
#   defined in the Evolution Loop section of SKILL.md.
#
# Never auto-edits SKILL.md. Promotion to the Synonym Map is a human decision.
set -eu

dir="$HOME/.openclaw/openclaw-docs"
log="$dir/misses.md"

if [ "${1:-}" = "--tally" ]; then
  top="${2:-20}"
  [ -f "$log" ] || { echo "no misses logged at $log" >&2; exit 0; }
  printf 'count  path\n'
  printf -- '-----  ----\n'
  # Only consider lines starting with an ISO-8601 UTC timestamp (real log rows,
  # not header text). Split on " | " and take field 3 (the path).
  awk -F' \\| ' '/^[0-9]{4}-[0-9]{2}-[0-9]{2}T[0-9]{2}:[0-9]{2}:[0-9]{2}Z / && NF==3 { print $3 }' "$log" \
    | sort | uniq -c | sort -rn | head -"$top" \
    | awk '{ count=$1; $1=""; sub(/^ +/, ""); printf "%5d  %s\n", count, $0 }'
  exit 0
fi

[ $# -lt 2 ] && {
  cat >&2 <<'USAGE'
usage:
  record_miss.sh "<question>" "<path>"   # record a miss
  record_miss.sh --tally [N]             # summarize top N paths (default 20)
USAGE
  exit 2
}

q="$1"
path="$2"
mkdir -p "$dir"
if [ ! -f "$log" ]; then
  printf '# Routing misses\n\nOne line per miss: timestamp | question | fetched path.\nPromote to SKILL.md Synonym Map only after 3 comparable hits.\nRun `record_miss.sh --tally` to see counts.\n\n' > "$log"
fi
ts=$(date -u +%Y-%m-%dT%H:%M:%SZ)
# Flatten newlines in the question so each miss stays on one line.
q_one=$(printf '%s' "$q" | tr '\n' ' ')
printf '%s | %s | %s\n' "$ts" "$q_one" "$path" >> "$log"
printf 'recorded: %s\n' "$log"


path: scripts/search.sh
content: #!/usr/bin/env bash
# Full-text search across all OpenClaw docs (case-insensitive, literal keyword).
# Usage: search.sh <keyword> [context-lines]
# Caches llms-full.txt for 1 hour at $TMPDIR/openclaw-llms-full.txt to avoid
# re-downloading on every call in a session (file is ~1MB+).
# Returns matching sections with surrounding context, capped at 200 lines.
# Keyword is matched as a fixed string (grep -F), so regex metacharacters and
# weird chars like '+', '?', '|', '{' are safe.
set -euo pipefail
[ $# -lt 1 ] && { echo "usage: $0 <keyword> [context-lines]" >&2; exit 2; }
kw="$1"
ctx="${2:-10}"
cache="${TMPDIR:-/tmp}/openclaw-llms-full.txt"
max_age=3600  # seconds

# Refresh cache if missing or older than max_age.
needs_fetch=1
if [ -f "$cache" ]; then
  if mtime=$(stat -f %m "$cache" 2>/dev/null || stat -c %Y "$cache" 2>/dev/null); then
    age=$(( $(date +%s) - mtime ))
    [ "$age" -lt "$max_age" ] && needs_fetch=0
  fi
fi
if [ "$needs_fetch" -eq 1 ]; then
  curl -sfL https://docs.openclaw.ai/llms-full.txt > "$cache.tmp"
  mv "$cache.tmp" "$cache"
fi

# Tolerate the expected exits: 0=match, 1=no-match, 141=SIGPIPE when head -200
# closes the pipe early. Any other grep error (e.g. unreadable cache) propagates.
set +e
grep -iF -A "$ctx" -B 2 -- "$kw" "$cache" | head -200
rc=$?
set -e
case $rc in 0|1|141) ;; *) exit "$rc" ;; esac


path: scripts/selftest.sh
content: #!/usr/bin/env bash
# Self-test: validate every doc path referenced in SKILL.md/EXAMPLES.md still
# resolves on docs.openclaw.ai.
# Run before publishing, or schedule in CI, to catch decision-tree rot.
# Exits non-zero if any path is STALE (3xx → 200, renamed) or MISSING (404/5xx).
#
# Paths are checked in parallel (default 8 workers, override with PARALLEL=N).
set -eu
here="$(cd "$(dirname "$0")" && pwd)"
root="$here/.."
skill="$root/SKILL.md"
examples="$root/EXAMPLES.md"
parallel="${PARALLEL:-8}"
[ -f "$skill" ] || { echo "SKILL.md not found at $skill" >&2; exit 2; }

if [ -f "$examples" ]; then
  stripped=$(cat "$skill" "$examples" | sed 's/`//g')
else
  stripped=$(sed 's/`//g' "$skill")
fi
paths=$( \
  { printf '%s\n' "$stripped" | grep -oE '→ [a-zA-Z0-9/_.-]+\.(md|json)' | sed 's/^→ //'; \
    printf '%s\n' "$stripped" | grep -oE ', [a-zA-Z0-9_-]+/[a-zA-Z0-9/_.-]+\.(md|json)' | sed 's/^, //'; \
  } | sort -u | sed '/^$/d')

# Per-path probe. Emits one line: STATUS<TAB>path[<TAB>extra].
#   OK       — direct 200
#   STALE    — direct 3xx, follows to 200 (rename; tree should be updated)
#   MISSING  — anything else
probe() {
  local p="$1"
  local url="https://docs.openclaw.ai/${p}"
  local direct
  direct=$(curl -sI -o /dev/null -w '%{http_code}' "$url" 2>/dev/null || echo ERR)
  if [ "$direct" = "200" ]; then
    printf 'OK\t%s\n' "$p"
  elif [ "${direct:0:1}" = "3" ]; then
    local final_url final_code
    final_url=$(curl -sIL -o /dev/null -w '%{url_effective}' "$url" 2>/dev/null || echo "")
    final_code=$(curl -sIL -o /dev/null -w '%{http_code}' "$url" 2>/dev/null || echo ERR)
    if [ "$final_code" = "200" ]; then
      printf 'STALE\t%s\t%s → %s\n' "$p" "$direct" "${final_url##https://docs.openclaw.ai/}"
    else
      printf 'MISSING\t%s\t%s, redirect chain ends %s\n' "$p" "$direct" "$final_code"
    fi
  else
    printf 'MISSING\t%s\t%s\n' "$p" "$direct"
  fi
}
export -f probe

results=$(printf '%s\n' "$paths" | xargs -n1 -P "$parallel" -I{} bash -c 'probe "$@"' _ {})

total=$(printf '%s\n' "$results" | wc -l | tr -d ' ')
stale=$(printf '%s\n' "$results" | awk -F'\t' '$1=="STALE"' | wc -l | tr -d ' ')
missing=$(printf '%s\n' "$results" | awk -F'\t' '$1=="MISSING"' | wc -l | tr -d ' ')

printf '%s\n' "$results" | awk -F'\t' '
  $1=="STALE"   { printf "  STALE    %-50s (%s)\n", $2, $3 }
  $1=="MISSING" { printf "  MISSING  %-50s (%s)\n", $2, $3 }
'

printf '\nchecked:    %d\nstale:      %d (3xx renames — update decision tree)\nmissing:    %d (404/5xx — page gone)\n' \
  "$total" "$stale" "$missing"

# Smoke test: fetch.sh must round-trip non-.md paths (.json was a regression).
# Pick one representative non-.md path from the discovered list; if none, skip.
non_md=$(printf '%s\n' "$paths" | grep -v '\.md$' | head -1 || true)
fetch_failed=0
if [ -n "$non_md" ]; then
  if ! "$here/fetch.sh" "$non_md" >/dev/null 2>&1; then
    printf 'fetch:      FAIL  fetch.sh %s (non-.md path)\n' "$non_md"
    fetch_failed=1
  else
    printf 'fetch:      OK    fetch.sh %s\n' "$non_md"
  fi
fi

[ "$missing" -eq 0 ] && [ "$stale" -eq 0 ] && [ "$fetch_failed" -eq 0 ] || exit 1


path: scripts/verify.sh
content: #!/usr/bin/env bash
# Verify the skill is seeing current OpenClaw docs.
# Prints page count, sha256, server Last-Modified, and local fetch timestamp —
# proves this is live, not cached.
set -eu
url=https://docs.openclaw.ai/llms.txt
tmp=$(mktemp)
hdr=$(mktemp)
trap 'rm -f "$tmp" "$hdr"' EXIT
curl -sfL -D "$hdr" "$url" > "$tmp"
pages=$(grep -c "^- \[" "$tmp" || true)
if command -v shasum >/dev/null 2>&1; then
  sha=$(shasum -a 256 "$tmp" | awk '{print $1}')
elif command -v sha256sum >/dev/null 2>&1; then
  sha=$(sha256sum "$tmp" | awk '{print $1}')
else
  sha="(no sha256 tool)"
fi
last_mod=$(awk 'BEGIN{IGNORECASE=1} /^last-modified:/ {sub(/^[^:]+: */, ""); sub(/\r$/, ""); print; exit}' "$hdr")
[ -z "$last_mod" ] && last_mod="(not advertised by server)"
printf 'source:        %s\npages:         %s\nsha256:        %s\nserver_mtime:  %s\nfetched:       %s\n' \
  "$url" "$pages" "$sha" "$last_mod" "$(date -u +%Y-%m-%dT%H:%M:%SZ)"


Security scan verdicts — ClawScan: clean | static analysis: clean | VirusTotal: clean | SkillSpector: suspicious (CRITICAL)