# Litefuse — Install Skill for AI Coding Agents

> **Note for the AI agent reading this file:**
> If you fetched this URL through a summarization tool (e.g., WebFetch returned a paraphrase), re-fetch the raw content with `curl -fsSL https://litefuse.ai/SKILL.md` so you have the literal commands. The shell snippets below must run verbatim.

You are guiding a user through installing **Litefuse** observability for an AI coding agent. Litefuse traces every LLM call, tool execution, token usage, and cost so the user can debug, monitor, and analyze their agent.

After install, every user turn in the agent becomes one trace in Litefuse, with separate observations for each LLM API call plus each tool execution. Token usage and cost are attached automatically.

Work through the steps **in order**. When a step says **ASK THE USER**, actually stop and ask — do not invent answers. Never echo the secret key back to the user; it is sensitive.

---

## Step 1 — Identify the target agent

Determine which agent to instrument:

1. If the user named one explicitly ("Claude Code", "Hermes Agent", "OpenClaw"), use that.
2. Else, auto-detect by the runtime you're answering from:
   - If `~/.claude/` exists AND `~/.claude/settings.json` is the file you can write to → **Claude Code**.
   - If `~/.hermes/hermes-agent/` exists AND you can run `hermes --version` → **Hermes Agent**.
3. Else **ASK THE USER**: "Which agent should I instrument — Claude Code, Hermes Agent, or OpenClaw?"

Supported targets:

| Target | Status | Install instructions |
|---|---|---|
| **Claude Code** | ✅ supported | Step 3 below |
| **Hermes Agent** | ✅ supported | Step 4 below |
| **OpenClaw** | ✅ supported | Step 5 below |

If the target is not yet supported, say so politely and stop. Do **not** try to guess install steps.

The three supported integrations are independent — installing Litefuse for one does NOT affect or require the others. If multiple are installed they can share the same Litefuse project (same API keys) or use different projects; the user picks.

---

## Step 2 — Get Litefuse credentials (shared across all targets)

You need three values:

- `LANGFUSE_PUBLIC_KEY` — starts with `pk-lf-`
- `LANGFUSE_SECRET_KEY` — starts with `sk-lf-`
- Base URL — defaults to `https://litefuse.cloud`. Each agent reads it from a different place: `LANGFUSE_BASE_URL` (Claude Code), `LANGFUSE_HOST` (Hermes), or the plugin's `baseUrl` config field (OpenClaw).

**Reuse-from-other-target shortcut**: before asking, probe each of the other supported agents on this machine for existing Litefuse keys:

```bash
# Probe Claude Code (~/.claude/settings.json)
if [ -f "$HOME/.claude/settings.json" ]; then
  python3 -c "
import json
try:
    e = (json.load(open('$HOME/.claude/settings.json')).get('env') or {})
    pk = e.get('LANGFUSE_PUBLIC_KEY', '')
    sk = e.get('LANGFUSE_SECRET_KEY', '')
    host = e.get('LANGFUSE_BASE_URL', '')
    if pk.startswith('pk-lf-') and sk.startswith('sk-lf-'):
        print(f'FOUND_IN_CLAUDE_CODE pk_prefix={pk[:10]} host={host}')
except Exception: pass
"
fi

# Probe Hermes Agent (~/.hermes/.env)
if [ -f "$HOME/.hermes/.env" ]; then
  pk=$(awk -F= '/^LANGFUSE_PUBLIC_KEY=/ {print $2}' "$HOME/.hermes/.env")
  sk=$(awk -F= '/^LANGFUSE_SECRET_KEY=/ {print $2}' "$HOME/.hermes/.env")
  host=$(awk -F= '/^LANGFUSE_(HOST|BASE_URL)=/ {print $2; exit}' "$HOME/.hermes/.env")
  case "$pk:$sk" in
    pk-lf-*:sk-lf-*) echo "FOUND_IN_HERMES pk_prefix=${pk:0:10} host=$host" ;;
  esac
fi

# Probe OpenClaw (~/.openclaw/openclaw.json) — keys live under the plugin entry
if [ -f "$HOME/.openclaw/openclaw.json" ]; then
  python3 -c "
import json
try:
    cfg = json.load(open('$HOME/.openclaw/openclaw.json'))
    entry = ((cfg.get('plugins') or {}).get('entries') or {}).get('openclaw-litefuse-plugin') or {}
    c = entry.get('config') or {}
    pk = c.get('publicKey', '')
    sk = c.get('secretKey', '')
    host = c.get('baseUrl', '')
    if pk.startswith('pk-lf-') and sk.startswith('sk-lf-'):
        print(f'FOUND_IN_OPENCLAW pk_prefix={pk[:10]} host={host}')
except Exception: pass
"
fi
```

Skip the probe for whichever agent you're currently installing for. If any other agent matched: **ASK THE USER**: "I found existing Litefuse keys configured for `<other-agent>`. Reuse them for `<target>`, or use a different project?" If reuse, skip to **Step 3** / **Step 4** / **Step 5** with those values. Don't display the secret key back — confirm only by `pk_prefix`.

If no existing keys, **ASK THE USER**: "Do you already have Litefuse API keys, or should I help you create new ones?"

### 2a. User already has keys

Ask them to paste:

- The public key
- The secret key
- (Optional) The base URL if they self-host. Default `https://litefuse.cloud`.

**Validate** before continuing:
- `pk-lf-` prefix; total length 44
- `sk-lf-` prefix; total length 44

If validation fails, ask again. Do **not** proceed with malformed keys.

### 2b. User needs to create keys

Give them this walkthrough verbatim:

> 1. Open <https://litefuse.cloud/auth/sign-up> in your browser
> 2. Sign up (Google / GitHub / email)
> 3. Click **+ New project**, pick any name
> 4. **Settings → API Keys → Create new API keys**
> 5. Copy the public key (`pk-lf-…`) and secret key (`sk-lf-…`)
> 6. Paste both back here

If you have browser-control tools available (e.g. `mcp__Claude_in_Chrome__*` for Claude Code, or the `web_extract` tool for Hermes), offer to open the sign-up URL. Otherwise just provide the URL.

Wait for the user to paste keys, then validate as in 2a.

**Important**: store the keys only in the target's config file (`~/.claude/settings.json` for Claude Code, `~/.hermes/.env` for Hermes Agent). Do not log them, do not echo the secret back to the user in chat, do not write them to any other file.

---

## Step 3 — Claude Code integration

Run these sub-steps in order. Each sub-step builds on the previous one.

### 3a. Verify `python3` is ≥ 3.10

Langfuse SDK v4 requires Python 3.10+. Use whichever Python `python3` resolves to on the user's PATH (don't pin to a specific minor version):

```bash
if ! command -v python3 >/dev/null 2>&1 \
    || ! python3 -c "import sys; sys.exit(0 if sys.version_info >= (3,10) else 1)" 2>/dev/null; then
  echo "ERROR: python3 ≥ 3.10 required; found: $(python3 --version 2>&1 || echo missing)"
fi
```

If the check fails, tell the user:

> Your `python3` is missing or older than 3.10. Install any 3.10+ version, make sure it's first on `PATH` as `python3`, then re-run me:
> - macOS: `brew install python@3.13` (Homebrew adds it to `PATH` as `python3`)
> - Debian/Ubuntu: `sudo apt install python3 python3-venv` — confirm with `python3 --version` that you get ≥ 3.10; older distros may ship 3.8/3.9, in which case use the deadsnakes PPA or your distro's newer package.
> - Or any asdf / pyenv / conda install of Python 3.10+.

…and STOP.

### 3b. Detect existing install + clean up old hook

```bash
# Litefuse's own hook already installed?
if [ -x "$HOME/.claude/hooks/.venv/bin/python3" ] \
    && [ -f "$HOME/.claude/hooks/litefuse_hook.py" ] \
    && grep -q 'litefuse_hook.py' "$HOME/.claude/settings.json" 2>/dev/null; then
  echo "ALREADY_INSTALLED"
fi

# Old upstream langfuse_hook.py from langfuse.com docs is BROKEN
# (imports `propagate_attributes` which is missing in older SDKs and
# silently no-ops via a bare except). Back it up so the user keeps a
# copy but it doesn't conflict.
if [ -f "$HOME/.claude/hooks/langfuse_hook.py" ]; then
  mv "$HOME/.claude/hooks/langfuse_hook.py" \
     "$HOME/.claude/hooks/langfuse_hook.py.bak.$(date +%Y%m%d-%H%M%S)"
  echo "Backed up old langfuse_hook.py"
fi
```

If `ALREADY_INSTALLED` printed, skip to **3f Verify**. Otherwise continue.

After 3e (settings.json merge), also remove any leftover Stop hook entry that points to the old `langfuse_hook.py` — the merge step handles this automatically by keying off the filename.

### 3c. Create a venv and install Langfuse SDK v4

```bash
python3 -m venv "$HOME/.claude/hooks/.venv"
"$HOME/.claude/hooks/.venv/bin/pip" install --upgrade pip
"$HOME/.claude/hooks/.venv/bin/pip" install 'langfuse>=4,<5'
```

Verify:

```bash
"$HOME/.claude/hooks/.venv/bin/python3" -c "import langfuse; print('langfuse', langfuse.__version__)"
# Expect: langfuse 4.x.y
```

If install fails (network, build error), surface the error to the user and STOP — don't try to work around it.

### 3d. Download the hook script

```bash
mkdir -p "$HOME/.claude/hooks"
curl -fsSL https://litefuse.ai/integrations/claude-code/litefuse_hook.py \
  -o "$HOME/.claude/hooks/litefuse_hook.py"
chmod +x "$HOME/.claude/hooks/litefuse_hook.py"
```

Smoke check:

```bash
"$HOME/.claude/hooks/.venv/bin/python3" -c "
import ast; ast.parse(open('$HOME/.claude/hooks/litefuse_hook.py').read())
print('hook syntax OK')
"
```

### 3e. Merge into `~/.claude/settings.json`

Do **not** overwrite the file blindly — it may hold other env vars and hooks the user cares about. Merge atomically with `jq` (substitute the user's `$PK`, `$SK`, and `$HOST`, where `$HOST` defaults to `https://litefuse.cloud`):

```bash
SETTINGS="$HOME/.claude/settings.json"
mkdir -p "$(dirname "$SETTINGS")"
[ -f "$SETTINGS" ] || echo '{}' > "$SETTINGS"

# Backup before mutating; settings.json holds the user's whole Claude
# Code config, mishaps are recoverable.
cp "$SETTINGS" "$SETTINGS.bak.$(date +%Y%m%d-%H%M%S)"

TMP=$(mktemp)
jq --arg pk "$PK" --arg sk "$SK" --arg host "$HOST" '
  .env = (.env // {}) + {
    "TRACE_TO_LANGFUSE": "true",
    "LANGFUSE_PUBLIC_KEY": $pk,
    "LANGFUSE_SECRET_KEY": $sk,
    "LANGFUSE_BASE_URL":   $host
  }
  | .hooks      = (.hooks // {})
  | .hooks.Stop = (.hooks.Stop // [])
  # Drop any prior Stop hook entry referencing the broken upstream
  # `langfuse_hook.py` — only our own `litefuse_hook.py` should remain.
  | .hooks.Stop |= map(select(
      (.hooks // []) | any((.command // "") | contains("langfuse_hook.py")) | not
    ))
  | (if any(.hooks.Stop[].hooks[]?; (.command // "") | contains("litefuse_hook.py"))
     then .
     else .hooks.Stop += [{
       "hooks": [{
         "type": "command",
         "command": "\"$HOME\"/.claude/hooks/.venv/bin/python3 \"$HOME\"/.claude/hooks/litefuse_hook.py"
       }]
     }]
     end)
' "$SETTINGS" > "$TMP" && mv "$TMP" "$SETTINGS"

# Secret key now lives in this file — tighten permissions.
chmod 600 "$SETTINGS"
```

If `jq` is not installed, ASK the user how to proceed — `brew install jq` / `apt install jq`, or offer to write a one-shot Python script that does the same merge. Do not hand-edit JSON via sed/awk; the file is structured and easy to corrupt.

### 3f. Verify

Smoke test the hook with the real env vars and empty stdin (fail-open path — the hook should exit 0 with no payload):

```bash
TRACE_TO_LANGFUSE=true \
LANGFUSE_PUBLIC_KEY="$PK" \
LANGFUSE_SECRET_KEY="$SK" \
LANGFUSE_BASE_URL="$HOST" \
"$HOME/.claude/hooks/.venv/bin/python3" "$HOME/.claude/hooks/litefuse_hook.py" < /dev/null
echo "exit=$?"
# Expect: exit=0; if you set CC_LANGFUSE_DEBUG=true, ~/.claude/state/litefuse_hook.log
# will show "Missing session_id or transcript_path; exiting." — that means imports
# and env vars are wired correctly.
```

Then tell the user, verbatim:

> ✅ Litefuse is installed.
>
> Claude Code re-reads `settings.json` on every hook firing, so **no restart is needed**. The Stop hook will fire when I finish this very message — your traces start appearing in Litefuse Cloud right away.
>
> **You can open <https://litefuse.cloud> → your project → Traces now** and watch them flow in as you keep chatting.
>
> Want me to also run an **automated check** on the latest trace? I can pull it via [`litefuse-cli`](https://www.npmjs.com/package/litefuse-cli) (no install required — I'll just use `npx`) and audit it for you: token usage attribution, observation tree, any missing fields. Just reply with **"verify trace"** in your next turn and I'll run it.
>
> Note: on the FIRST Stop hook firing in a long-running session, the hook catches up on the entire transcript and emits one trace per past turn. Use `/clear` first if you want a single clean trace from scratch.

Then skip Step 4 (you're done) and go to **Step 5**.

---

## Step 4 — Hermes Agent integration

Hermes runs as a Python plugin under `~/.hermes/plugins/litefuse/`. It loads in-process inside Hermes' own venv (not a separate one) and subscribes to Hermes lifecycle hook events. Real wall-clock timestamps, single code path across CLI / Gateway / TUI.

### 4a. Verify Hermes is installed

```bash
hermes --version 2>&1 | head -3
ls -d "$HOME/.hermes/hermes-agent" >/dev/null 2>&1 && echo "HERMES_HOME_OK"
```

If `hermes` is missing or `~/.hermes/hermes-agent/` doesn't exist, tell the user:

> I can't find a Hermes Agent install. Install it first (`pip install hermes-agent` or follow https://hermes-agent.nousresearch.com/docs/getting-started ) and re-run me.

…and STOP.

### 4b. Detect existing install

```bash
if [ -f "$HOME/.hermes/plugins/litefuse/__init__.py" ] \
    && [ -f "$HOME/.hermes/plugins/litefuse/plugin.yaml" ]; then
  echo "ALREADY_INSTALLED"
fi
```

If `ALREADY_INSTALLED` printed, skip to **4f Enable** to make sure it's enabled, then **4g Verify**. Otherwise continue.

### 4c. Install Langfuse SDK v4 into Hermes' venv

The plugin runs in-process inside Hermes' Python interpreter, so the SDK has to live in Hermes' venv (not a separate one):

```bash
"$HOME/.hermes/hermes-agent/venv/bin/pip" install 'langfuse>=4,<5'

# Verify
"$HOME/.hermes/hermes-agent/venv/bin/python3" -c "import langfuse; print('langfuse', langfuse.__version__)"
# Expect: langfuse 4.x.y
```

If install fails (network, build error), surface the error and STOP.

### 4d. Download the plugin files

```bash
mkdir -p "$HOME/.hermes/plugins/litefuse"
curl -fsSL https://litefuse.ai/integrations/hermes-agent/plugin.yaml \
  -o "$HOME/.hermes/plugins/litefuse/plugin.yaml"
curl -fsSL https://litefuse.ai/integrations/hermes-agent/__init__.py \
  -o "$HOME/.hermes/plugins/litefuse/__init__.py"
```

Smoke check:

```bash
"$HOME/.hermes/hermes-agent/venv/bin/python3" -c "
import ast; ast.parse(open('$HOME/.hermes/plugins/litefuse/__init__.py').read())
print('plugin syntax OK')
"
```

### 4e. Add credentials to `~/.hermes/.env`

The plugin reads credentials from Hermes' own `.env` file (Hermes auto-loads it at startup). Append or update; preserve any other keys the user already has:

```bash
ENVFILE="$HOME/.hermes/.env"
touch "$ENVFILE"
cp "$ENVFILE" "$ENVFILE.bak.$(date +%Y%m%d-%H%M%S)"

# Remove any pre-existing Litefuse lines, then re-append clean ones.
# Use a Python script so we don't accidentally munge other env entries.
python3 - "$ENVFILE" "$PK" "$SK" "$HOST" <<'PY'
import sys
path, pk, sk, host = sys.argv[1:]
keep_lines = []
for line in open(path).read().splitlines():
    if line.startswith(("LANGFUSE_PUBLIC_KEY=",
                        "LANGFUSE_SECRET_KEY=",
                        "LANGFUSE_HOST=",
                        "LANGFUSE_BASE_URL=")):
        continue
    keep_lines.append(line)
if keep_lines and keep_lines[-1].strip() != "":
    keep_lines.append("")
keep_lines.extend([
    "# Litefuse observability",
    f"LANGFUSE_PUBLIC_KEY={pk}",
    f"LANGFUSE_SECRET_KEY={sk}",
    f"LANGFUSE_HOST={host}",
])
open(path, "w").write("\n".join(keep_lines) + "\n")
print("env file updated")
PY

chmod 600 "$ENVFILE"
```

### 4f. Enable the plugin

Hermes plugins are opt-in:

```bash
hermes plugins enable litefuse
hermes plugins list | grep -E '^│ *litefuse' || hermes plugins list
# Expect: a row showing `litefuse` with Status = `enabled`
```

### 4g. Restart the gateway if it's running

The gateway daemon caches its plugin manager at startup, so a restart is needed for the new plugin to load. CLI invocations don't need this — they load plugins fresh per process.

```bash
if hermes gateway status 2>/dev/null | grep -q "Gateway service is loaded"; then
  hermes gateway restart
  echo "gateway restarted"
fi
```

### 4h. Verify

Fire a one-shot CLI turn and check the plugin log:

```bash
hermes chat -q "say hello in five words exactly" 2>&1 | tail -3
sleep 2
tail -3 "$HOME/.hermes/state/litefuse_plugin.log"
# Expect: "litefuse plugin registered (9 hooks)"
#       + "Litefuse client ready (host=https://litefuse.cloud)"
#       + "turn complete session=YYYYMMDD_HHMMSS_xxxxxx turn=1 api_calls=1 tool_calls=0"
```

If the log doesn't show "turn complete", inspect for errors:

```bash
tail -20 "$HOME/.hermes/state/litefuse_plugin.log"
```

Then tell the user, verbatim:

> ✅ Litefuse plugin installed for Hermes Agent.
>
> Hermes loaded the plugin on the verification turn above — your first trace named `Hermes Agent — Turn 1` is in Litefuse Cloud already.
>
> **You can open <https://litefuse.cloud> → your project → Traces now** to see it.
>
> Coverage notes for the current Hermes version:
> - **CLI** (`hermes chat -q`): ✅ works out of the box.
> - **Gateway** (Feishu / Slack / Discord / Telegram / etc.): ✅ works after the restart in step 4g.
> - **TUI** (`hermes chat --tui`): only if you're on a Hermes version with [PR #24237](https://github.com/NousResearch/hermes-agent/pull/24237) merged. Older versions don't load plugins in the TUI process; nothing to fix on your side — wait for the upstream merge or upgrade.
> - **Oneshot** (`hermes -z`): same caveat — `oneshot.py` doesn't `discover_plugins()` in current Hermes; use `hermes chat -q` instead for one-shot prompts.
>
> Want me to also run an **automated check** on the latest trace? I can pull it via [`litefuse-cli`](https://www.npmjs.com/package/litefuse-cli) (no install required — I'll just use `npx` or `bunx`) and audit it for you: token usage attribution, observation tree, any missing fields. Just reply with **"verify trace"** in your next turn and I'll run it.

---

## Step 5 — OpenClaw integration

OpenClaw uses an in-process TypeScript plugin under `~/.openclaw/extensions/` (or any path linked via `openclaw plugins install --link`). It subscribes to the gateway's lifecycle hooks (`message_received`, `llm_input`/`llm_output`, `before_tool_call`/`after_tool_call`, `before_agent_start`/`agent_end`, …) and emits one Litefuse trace per agent turn.

### 5a. Verify OpenClaw is installed and configured for local mode

```bash
command -v openclaw >/dev/null 2>&1 || { echo "ERROR: openclaw CLI not on PATH"; }
openclaw --version 2>&1 | head -1
openclaw config get gateway.mode 2>&1 | tail -1
# Expect: gateway.mode = "local"
```

If `openclaw` is missing, tell the user:

> I can't find an OpenClaw install. Install it first (`npm install -g openclaw` or follow https://docs.openclaw.ai/install ) and re-run me.

…and STOP.

If `gateway.mode` is unset, set it:

```bash
openclaw config set gateway.mode local
```

Also verify Node.js ≥ 22 is on PATH — the plugin's TypeScript build requires it:

```bash
node -v | awk -F. '{ sub("v","",$1); exit ($1<22) }' || echo "ERROR: node ≥ 22 required"
```

### 5b. Detect existing install

```bash
if openclaw plugins inspect openclaw-litefuse-plugin >/dev/null 2>&1; then
  echo "ALREADY_INSTALLED"
fi
```

If `ALREADY_INSTALLED` printed, skip to **5e Configure** (in case credentials need refreshing), then **5g Verify**. Otherwise continue.

### 5c. Clone and build the plugin

The repo ships TypeScript only — `dist/` is gitignored, so build once before linking. Pick any stable location for the source tree; `~/.openclaw/extensions/openclaw-litefuse-plugin` is the convention:

```bash
SRC="$HOME/.openclaw/extensions/openclaw-litefuse-plugin"
if [ ! -d "$SRC/.git" ]; then
  mkdir -p "$(dirname "$SRC")"
  git clone https://github.com/litefuse/openclaw-litefuse-plugin.git "$SRC"
fi
cd "$SRC"
git pull --ff-only 2>/dev/null || true
npm install
npm run build

# Sanity check the build emitted dist/index.js
test -f "$SRC/dist/index.js" || echo "ERROR: build did not produce dist/index.js"
```

If `npm install` or `npm run build` fails (network, toolchain), surface the error to the user and STOP — don't try to work around it.

### 5d. Link the plugin into OpenClaw

```bash
openclaw plugins install --link "$SRC" 2>&1 | tail -3
openclaw plugins inspect openclaw-litefuse-plugin 2>&1 | head -10
# Expect: Status: loaded
#         Source: <SRC>/dist/index.js
```

The first `--link` install may also emit a `[Litefuse] Missing required configuration` warning — that's expected before 5e completes; ignore it.

### 5e. Configure credentials in `~/.openclaw/openclaw.json`

OpenClaw's `config set` performs a structured atomic merge, so it won't clobber other entries. Set the three required values (substitute the user's `$PK`, `$SK`, and `$HOST`, where `$HOST` defaults to `https://litefuse.cloud`):

```bash
openclaw config set plugins.entries.openclaw-litefuse-plugin.config.publicKey "$PK"
openclaw config set plugins.entries.openclaw-litefuse-plugin.config.secretKey "$SK"
openclaw config set plugins.entries.openclaw-litefuse-plugin.config.baseUrl   "$HOST"
openclaw config set plugins.entries.openclaw-litefuse-plugin.config.environment "production"

# Tighten permissions — openclaw.json now holds the secret key.
chmod 600 "$HOME/.openclaw/openclaw.json"
```

The secret key now lives in `~/.openclaw/openclaw.json` only. Do **not** echo it back to the user; confirm only by `pk_prefix`.

### 5f. Restart the gateway

The gateway caches plugin config at startup, so a restart is required for the new credentials to apply:

```bash
if openclaw gateway restart 2>&1 | grep -q "not loaded"; then
  # Service hasn't been installed as a launchd/systemd unit yet.
  # Start it in the foreground (user can later run `openclaw gateway install`).
  pkill -f "openclaw gateway" 2>/dev/null; sleep 1
  nohup openclaw gateway >/tmp/openclaw-gateway.log 2>&1 &
  sleep 5
fi

# Confirm it's responding.
openclaw health 2>&1 | head -3
```

### 5g. Verify

`openclaw plugins doctor` re-instantiates each plugin in isolation and surfaces activation logs — the fastest sanity check before running a real turn:

```bash
openclaw plugins doctor 2>&1 | grep Litefuse
# Expect three lines:
#   [Litefuse] Target added: <host>
#   [Litefuse] Plugin initialized with 1 target(s)
#   [Litefuse] Plugin activated (baseUrl: <host>)
```

If those three lines don't appear, inspect the gateway log:

```bash
LOGFILE=$(ls -t /tmp/openclaw/openclaw-*.log 2>/dev/null | head -1)
[ -n "$LOGFILE" ] && grep -E '"message":"\[Litefuse\]' "$LOGFILE" | tail -5
```

Then fire a real agent turn so the plugin can flush a complete trace. Use whichever model provider the user already has authed (`openclaw infer model auth status | jq '.auth.providers[]'`); fall back to asking them. For example, if Anthropic is configured:

```bash
SID="litefuse-verify-$(date +%s)"
openclaw agent --model "anthropic/claude-haiku-4-5" \
  --session-id "$SID" \
  -m "say 'hello' and nothing else" 2>&1 | tail -10
```

Confirm the trace landed via the Litefuse API (auth happens via the same pk/sk):

```bash
sleep 2
curl -sS -u "$PK:$SK" \
  "$HOST/api/public/traces?limit=1&tags=openclaw" \
  | python3 -c "import sys,json; d=json.load(sys.stdin)['data']; print(d[0]['id'], d[0]['name'], d[0]['timestamp']) if d else print('NO_TRACE_YET')"
# Expect: <traceId> openclaw-<8hex> <ISO timestamp>
```

If the curl returns `NO_TRACE_YET`, look for `[Litefuse] Flushed 1 target(s)` in the gateway log — that confirms the SDK attempted to send. The most common cause of `Flushed` never appearing is the LLM call failing before `agent_end` fires; check `lane task error` in the same log.

Then tell the user, verbatim:

> ✅ Litefuse plugin installed for OpenClaw.
>
> Your first trace `openclaw-<8hex>` is in Litefuse Cloud already.
>
> **You can open <https://litefuse.cloud> → your project → Traces now** to see it.
>
> Coverage notes:
> - **`openclaw agent` turns** (`--local` or via the gateway): ✅ each user message becomes one trace named `openclaw-<8hex>` with nested generation + tool observations.
> - **TUI / `openclaw chat`**: ✅ same plugin code path; sessions are grouped by `sessionId`.
> - **Channels** (Telegram, Slack, Discord, …): ✅ once the gateway is up; channel id is captured under `openclaw.channel.id` metadata.
> - **Multi-target**: set `plugins.entries.openclaw-litefuse-plugin.config.targets` to an array if you need to fan traces out to multiple Litefuse projects.
>
> Want me to also run an **automated check** on the latest trace? I can pull it via [`litefuse-cli`](https://www.npmjs.com/package/litefuse-cli) (no install required — I'll just use `npx`) and audit it for you: token usage attribution, observation tree, any missing fields. Just reply with **"verify trace"** in your next turn and I'll run it.

---

## Step 6 — Report what changed

Summarize for the user (omit the secret key from the summary).

For **Claude Code**:
- **Python interpreter**: `$(command -v python3)` (`$(python3 --version)`)
- **Virtualenv**: `~/.claude/hooks/.venv` (`langfuse` v4.x)
- **Hook script**: `~/.claude/hooks/litefuse_hook.py` (downloaded from https://litefuse.ai)
- **settings.json**: merged in `env.TRACE_TO_LANGFUSE`, `env.LANGFUSE_PUBLIC_KEY`, `env.LANGFUSE_SECRET_KEY` (redacted), `env.LANGFUSE_BASE_URL`, and a Stop hook entry. Existing entries were preserved.

End with: "Your secret key now lives in `~/.claude/settings.json`. Keep that file readable only by you (`chmod 600`)."

For **Hermes Agent**:
- **Plugin**: `~/.hermes/plugins/litefuse/{plugin.yaml, __init__.py}` (downloaded from https://litefuse.ai)
- **Langfuse SDK**: installed into Hermes' own venv (`~/.hermes/hermes-agent/venv/`)
- **`~/.hermes/.env`**: appended `LANGFUSE_PUBLIC_KEY`, `LANGFUSE_SECRET_KEY` (redacted), `LANGFUSE_HOST`. Existing entries preserved; backup at `~/.hermes/.env.bak.<timestamp>`.
- **`~/.hermes/config.yaml`**: `plugins.enabled` updated by `hermes plugins enable litefuse` to include `litefuse`.
- **Gateway**: restarted if it was running.

End with: "Your secret key now lives in `~/.hermes/.env`. Keep that file readable only by you (`chmod 600`)."

For **OpenClaw**:
- **Plugin source**: `~/.openclaw/extensions/openclaw-litefuse-plugin` (cloned from https://github.com/litefuse/openclaw-litefuse-plugin)
- **Build output**: `~/.openclaw/extensions/openclaw-litefuse-plugin/dist/index.js` (produced by `npm run build`)
- **Plugin linked into OpenClaw**: registered as `openclaw-litefuse-plugin` via `openclaw plugins install --link`. Confirm with `openclaw plugins inspect openclaw-litefuse-plugin`.
- **`~/.openclaw/openclaw.json`**: merged in `plugins.entries.openclaw-litefuse-plugin.config.{publicKey, secretKey (redacted), baseUrl, environment}`. Existing entries preserved by `openclaw config set`.
- **`gateway.mode`**: ensured to be `local` (set if missing).
- **Gateway**: restarted to pick up the new config.

End with: "Your secret key now lives in `~/.openclaw/openclaw.json`. Keep that file readable only by you (`chmod 600`)."

---

## Step 7 — Follow-up verification (when the user comes back next turn)

By the time the user asks "verify trace" / "check litefuse" / "did it work", one or more traces have been emitted. Run via `npx` (or `bunx` / `pnpm dlx` if they prefer) — no global install needed. The npm package is `litefuse-cli` and the binary it ships is `litefuse`:

```bash
# Use npx -y to skip the "install package?" prompt; the package gets cached
# after the first run. Substitute `bunx litefuse-cli` or `pnpm dlx litefuse-cli`
# if those are present and preferred.
LITEFUSE="npx -y litefuse-cli"

# Pull the most recent trace
LATEST=$($LITEFUSE api traces list --limit 1 | jq -r '.data[0].id')
echo "Latest trace: $LATEST"
$LITEFUSE api traces get "$LATEST" > /tmp/litefuse_verify.json
```

Run an audit on `/tmp/litefuse_verify.json`. The exact shape depends on which integration the user installed:

**Claude Code traces:**
- Trace **name** starts with `Claude Code - Turn`
- Top-level `sessionId`, `userId`, `release`, `tags` populated
- Observation breakdown: 1 SPAN root + 1 EVENT `user message` + N generations + M tools, **ending with `Final response (#N)`** generation. If the trace ends with something other than a `Final response` (e.g., a `Decision to call tool` or `Tool call`), the turn was emitted prematurely and the hook is on an older version — re-download from `https://litefuse.ai/integrations/claude-code/litefuse_hook.py`.
- Every GENERATION has a `model`
- Every GENERATION with `metadata.claude_code.is_last_in_message == true` has a non-empty `usageDetails`
- Every TOOL has `metadata.claude_code.uuid` and `metadata.claude_code.parentUuid`
- `metadata.claude_code.tool_use_id` cross-references the corresponding generation's tool_use block
- **Metadata is sparse**: fields absent from the source JSONL row are NOT padded with `null` — e.g., a normal assistant row's metadata won't have `claude_code.apiErrorStatus`, `claude_code.error`, `claude_code.stop_details` keys at all. Only fields that genuinely appeared in JSONL are present.

**Hermes Agent traces:**
- Trace **name** matches `Hermes Agent — Turn N`
- Top-level `sessionId` (format `YYYYMMDD_HHMMSS_<6or8hex>`), `userId`, `tags` populated
- Tags include `hermes-agent`, `litefuse`, and one `model:<name>` entry
- Observation breakdown: 1 SPAN root + 1 EVENT `user message` + N `api: <model> #N` generations + M `tool: <name> (#K)` tools + 1 `assistant response` generation
- Every `api: ...` GENERATION has `usageDetails` (input_tokens, output_tokens, plus optional cache tokens)
- Every TOOL has `metadata.hermes_agent.{tool_name, duration_ms}`
- Every observation's `metadata.hermes_agent.*` (not top-level metadata) carries the Hermes-specific fields

**OpenClaw traces:**
- Trace **name** matches `openclaw-<8hex>` (first 8 chars of the traceId)
- Top-level `tags` includes `openclaw`; `environment` matches the configured value
- Top-level `sessionId` is the OpenClaw `sessionId` (set after `session_start` / `agent_end`); `userId` is the message sender, optionally prefixed by the configured `userId` (e.g. `alice/<sender>`)
- Observation breakdown: 1 root SPAN `enter_openclaw_system` + 1 nested SPAN `invoke_agent <agentId>` + N GENERATION `chat <model>` + M TOOL `execute_tool <name>` + optional session/gateway EVENTs
- Every `chat <model>` GENERATION has a non-empty `model` and a `usageDetails` block. For Anthropic models, expect `input`, `output`, `cache_read_input_tokens`, and `cache_creation_input_tokens` keys
- Every `execute_tool <name>` is observation-type `TOOL` (not a plain span) — required for Litefuse's graph view
- Every observation's `metadata` carries `openclaw.run.id`, `openclaw.turn.id`, `openclaw.channel.id`, and `openclaw.version`
- The agent span's `metadata` includes `agent.duration_ms`, `agent.message_count`, `agent.success`; on failure it also has `agent.error`

If any check fails, surface the specific observation id + the failure. For Claude Code, ask the user to share `~/.claude/state/litefuse_hook.log`. For Hermes Agent, ask for `~/.hermes/state/litefuse_plugin.log`. For OpenClaw, ask for the gateway log lines matching `[Litefuse]` from `/tmp/openclaw/openclaw-$(date +%Y-%m-%d).log`. If `totalCost` is 0, explain that's because their Litefuse project doesn't have a price config for the model — point them at **Settings → Models** in the Litefuse UI.

---

## For Litefuse maintainers — extending this SKILL

To add support for a new agent:

1. **Step 1**: change the agent's row in the support table from ⏳ to ✅ and point it at the new section.
2. **Step 2 is shared** — the credential flow doesn't need duplication. Extend the reuse-from-other-target probe block to also check the new agent's config file, so credentials carry across all installed targets.
3. **Append a new "Step N: <Agent> integration"** mirroring Step 3 (Claude Code), Step 4 (Hermes), or Step 5 (OpenClaw) substep structure:
   - `Na. Detect runtime` (e.g., binary on PATH, venv, config dir)
   - `Nb. Idempotency check`
   - `Nc. Install dependencies` (whatever the agent needs)
   - `Nd. Download / link the hook or plugin` (from `https://litefuse.ai/integrations/<agent>/<filename>` for hosted scripts, or from the agent-specific GitHub repo for full plugins)
   - `Ne. Configure` (the agent's config file — merge, don't clobber)
   - `Nf. Restart / enable` (if the agent has plugin opt-in or daemon semantics)
   - `Ng. Verify`
4. **Renumber** the existing report + follow-up steps to keep them last.
5. **Step "Report what changed"**: add a per-agent block enumerating what files were touched and where the secret key now lives.
6. **Step "Follow-up verification"**: add a per-agent block listing the trace name pattern, expected observation tree, and required metadata keys for `litefuse-cli` auditing.
7. If a section grows beyond ~80 lines, split into a sub-skill at `https://litefuse.ai/integrations/<agent>/SKILL.md` and have Step 1 instruct the AI agent to fetch that URL.
8. Keep host scripts alongside the docs: `public/integrations/<agent>/<filename>` → served at `https://litefuse.ai/integrations/<agent>/<filename>`. Full plugins that need a build step (e.g. OpenClaw's TypeScript plugin) live in their own GitHub repo; reference the repo URL directly in the install step.

The dispatch pattern (Step 1 → identify → branch) is intentionally simple: one file, single fetch, predictable for the AI agent.
