Use when setting up clawtel to report token usage from a project that calls the Anthropic API (SDK, Claude Code, or any tapes-wrapped agent) to the claw.tech...
---
name: clawtel-setup
description: Use when setting up clawtel to report token usage from a project that calls the Anthropic API (SDK, Claude Code, or any tapes-wrapped agent) to the claw.tech leaderboard. Covers install, env vars, tapes wiring, verification, and running as a persistent service.
version: 1.2.0
user-invocable: true
metadata:
openclaw:
requires:
bins: ["tapes"]
env: ["CLAW_INGEST_KEY", "CLAW_ID"]
primaryEnv: CLAW_INGEST_KEY
emoji: "๐ฆ"
homepage: https://github.com/bdougie/clawtel
---
# clawtel setup
Get clawtel reporting token usage from any project that calls Anthropic (SDK, Claude Code, openclaw, staffchief-style agents) to the claw.tech leaderboard.
clawtel is a single Go binary that reads aggregate token counts from a local tapes SQLite database and sends heartbeats to `https://ingest.claw.tech/v1/heartbeat`. It never reads prompt or response content โ only `created_at`, `model`, `prompt_tokens`, `completion_tokens` from the `nodes` table.
## When to use
- A project is calling the Anthropic API (directly via SDK, via Claude Code, or via any agent framework) and you want its usage on the claw.tech leaderboard
- The project already has or can run `tapes` to capture model calls
- Setting up a new claw agent (e.g. staffchief, clawchief, openclaw-in-a-box) and wiring telemetry end-to-end
- Debugging why claw.tech shows no heartbeats for a running agent
## Prerequisites
Before running clawtel you need three things:
1. **tapes** installed and running as a proxy in front of Anthropic. If tapes isn't there yet:
```bash
curl -fsSL https://download.tapes.dev/install | bash
tapes init
```
2. **A claw.tech ingest key**. Register your claw at https://claw.tech and copy the `ik_...` key shown once at creation.
3. **An Anthropic-calling workload**. Could be a Node/Python app importing `@anthropic-ai/sdk` / `anthropic`, Claude Code, or an openclaw agent. Anything that hits `api.anthropic.com`.
## Step 1 โ wire tapes in front of Anthropic
clawtel only sees usage that tapes has recorded. Point your Anthropic client at the tapes proxy so every call flows through it:
```bash
# In the shell where your agent runs
export ANTHROPIC_BASE_URL="http://localhost:8080"
tapes start # starts proxy + API, writes to ~/.tapes/tapes.sqlite
```
For the Anthropic Node/Python SDK, `ANTHROPIC_BASE_URL` (or passing `baseURL` to the client constructor) is enough โ no code changes. For Claude Code, set it before launching the CLI.
### OpenClaw users: `ANTHROPIC_BASE_URL` alone is not enough
If your workload is an OpenClaw agent (clawchief, staffchief, openclaw-in-a-box), the env var is silently ignored. OpenClaw instantiates its Anthropic client with `baseURL: model.baseUrl`, which clobbers the SDK's normal `readEnv("ANTHROPIC_BASE_URL")` fallback. You'll see heartbeats sending with `model=""`, `input_tokens=0`, `output_tokens=0` forever, and the gateway process will hold a direct TLS connection to Anthropic's edge instead of `127.0.0.1:8080`.
Fix: set the provider's base URL explicitly in `~/.openclaw/openclaw.json`:
```json
{
"models": {
"providers": {
"anthropic": {
"baseUrl": "http://localhost:8080",
"models": [
{ "id": "claude-opus-4-6", "name": "Claude Opus 4.6" },
{ "id": "claude-sonnet-4-6", "name": "Claude Sonnet 4.6" },
{ "id": "claude-haiku-4-5", "name": "Claude Haiku 4.5" }
]
}
}
}
}
```
Then restart the OpenClaw gateway. Verify the gateway is routing through tapes:
```bash
ss -tnp | awk -v pid="$(pgrep -f openclaw-gateway | head -1)" '$0 ~ "pid="pid'
# Expect a line with Peer Address 127.0.0.1:8080
# If instead you see Peer Address 160.79.*.* or a Cloudflare IP, tapes is still being bypassed.
```
Confirm tapes is capturing rows:
```bash
sqlite3 ~/.tapes/tapes.sqlite \
'SELECT count(*), max(created_at) FROM nodes;'
```
If the count is zero after making a call, tapes isn't in the request path โ recheck `ANTHROPIC_BASE_URL` in the process actually running the agent (and for OpenClaw, the `models.providers.anthropic.baseUrl` config above).
## Step 2 โ install clawtel
Pick one of the two options below. The verified path is recommended for production boxes; the convenience one-liner is fine for a laptop where you've already read the install script.
### Option A โ verified install (pinned release + checksum)
Every release tag publishes a `checksums.txt` alongside the OS/arch tarballs. Download the release for your platform, verify against the checksum file, then move the binary onto your `PATH`:
```bash
# Adjust VERSION and PLAT for your box. Releases: https://github.com/bdougie/clawtel/releases
VERSION=v0.1.9
PLAT=darwin_arm64 # or linux_amd64, linux_arm64, darwin_amd64
BASE=https://github.com/bdougie/clawtel/releases/download/${VERSION}
curl -fsSLO "${BASE}/clawtel_${PLAT}.tar.gz"
curl -fsSLO "${BASE}/checksums.txt"
# Verify. sha256sum on Linux, shasum -a 256 on macOS.
grep "clawtel_${PLAT}.tar.gz" checksums.txt | sha256sum -c - # or: shasum -a 256 -c -
tar -xzf "clawtel_${PLAT}.tar.gz"
install -m 755 clawtel "$HOME/.local/bin/clawtel" # or /usr/local/bin with sudo
```
### Option B โ convenience one-liner (`curl | bash`)
The install script is auditable in one read: <https://raw.githubusercontent.com/bdougie/clawtel/main/scripts/install.sh>. Under the hood it does the same thing as Option A โ resolves the latest release, downloads the matching tarball, and drops the binary into `/usr/local/bin` (or `CLAWTEL_INSTALL_DIR`).
```bash
curl -fsSL https://raw.githubusercontent.com/bdougie/clawtel/main/scripts/install.sh | bash
```
Or to a user directory without sudo:
```bash
CLAWTEL_INSTALL_DIR="$HOME/.local/bin" \
curl -fsSL https://raw.githubusercontent.com/bdougie/clawtel/main/scripts/install.sh | bash
```
Verify either path with: `clawtel --version` should print a version string that matches the release you intended to install.
## Step 3 โ set environment variables
```bash
export CLAW_ID="your-claw-name"
export CLAW_INGEST_KEY="ik_..."
# Only if tapes.sqlite lives somewhere non-standard:
export TAPES_DB="/custom/path/tapes.sqlite"
```
Put these in `~/.zshrc`, `~/.bashrc`, `/etc/environment`, or a systemd `EnvironmentFile` โ wherever your agent process reads env from. **No key, no network calls** โ clawtel exits silently if `CLAW_INGEST_KEY` is unset, so double-check it's exported in the right scope.
Database path resolution order:
1. `TAPES_DB` env var
2. `.mb/tapes/tapes.sqlite` (openclaw-in-a-box layout)
3. `~/.tapes/tapes.sqlite` (standalone tapes install)
## Step 4 โ run clawtel
For a quick check on your laptop, run it in the foreground:
```bash
clawtel
```
Expected startup log:
```
clawtel: clawtel 0.1.x
clawtel: db: /home/you/.tapes/tapes.sqlite
clawtel: cursor: /home/you/.tapes/clawtel/cursor
clawtel: claw: your-claw-name
clawtel: reads: created_at, model, prompt_tokens, completion_tokens (from nodes table)
clawtel: sends: tokens + model counts only. no prompts. no responses.
clawtel: NOTE: nodes table has column "content" โ clawtel does NOT read it
clawtel: polling every 1h
```
Any `NOTE:` lines listing sensitive columns are **good** โ they confirm clawtel sees those columns and is deliberately ignoring them.
## Step 5 โ run as a persistent service
For long-running agents (droplets, servers, home boxes), run clawtel under systemd so it survives reboots. Example unit at `/etc/systemd/system/clawtel.service`:
```ini
[Unit]
Description=clawtel โ token telemetry for claw.tech
After=network-online.target
Wants=network-online.target
[Service]
Type=simple
User=root
EnvironmentFile=/etc/clawtel.env
ExecStart=/usr/local/bin/clawtel
Restart=on-failure
RestartSec=10
[Install]
WantedBy=multi-user.target
```
Create `/etc/clawtel.env` with `0600` permissions:
```
CLAW_ID=your-claw-name
CLAW_INGEST_KEY=ik_...
TAPES_DB=/root/.tapes/tapes.sqlite
```
Then:
```bash
chmod 600 /etc/clawtel.env
systemctl daemon-reload
systemctl enable --now clawtel
journalctl -u clawtel -f
```
On the same machine as an agent like clawchief/staffchief, `TAPES_DB` should point at the same sqlite file the agent's tapes proxy is writing to.
## Step 6 โ verify the heartbeat reaches claw.tech
1. Trigger a real model call in your agent (send a message, run `claude` once, let the cron fire).
2. Wait up to a full poll interval (currently 1 hour) or stop/start clawtel to force an immediate send.
3. Check `journalctl -u clawtel` (systemd) or the foreground log for a `sent heartbeat` line with non-zero `input_tokens`/`output_tokens`.
4. Load your profile on claw.tech โ the leaderboard row should update within a minute of a successful heartbeat.
## Common issues
| Symptom | Likely cause | Fix |
|---|---|---|
| clawtel exits silently on start | `CLAW_INGEST_KEY` not exported in this shell/service | Check `systemctl show clawtel -p Environment` or `env \| grep CLAW_` |
| `assertSchema failed` on startup | tapes.sqlite is missing required columns or points at the wrong file | Confirm with `sqlite3 $TAPES_DB '.schema nodes'` that `created_at, model, prompt_tokens, completion_tokens` all exist |
| Heartbeats send but all zeros, agent is an OpenClaw gateway | `ANTHROPIC_BASE_URL` is set but OpenClaw overrides it with `model.baseUrl` (unset) | Set `models.providers.anthropic.baseUrl = "http://localhost:8080"` in `~/.openclaw/openclaw.json` (see Step 1 OpenClaw note) |
| Heartbeats send but all zeros (non-OpenClaw) | tapes isn't proxying Anthropic calls โ agent is talking directly to `api.anthropic.com` | Verify `ANTHROPIC_BASE_URL` in the agent process environment; re-check `SELECT count(*) FROM nodes` |
| `401` from ingest endpoint | Wrong or rotated ingest key | Regenerate on claw.tech and update `CLAW_INGEST_KEY` |
| Leaderboard shows offline despite running clawtel | Two consecutive missed heartbeats (>2ร poll interval) | Check `journalctl -u clawtel` for network errors; confirm outbound HTTPS to `ingest.claw.tech` works |
| Cursor seems stuck | `~/.tapes/clawtel/cursor` is not writable by the clawtel user | `chown` the directory to the service user |
| Cursor seems stuck on clawtel < 0.1.4 | Cursor is written as RFC3339Nano but tapes stores timestamps with a space separator; SQLite string comparison fails so the poll returns zero rows forever (see issue #4) | Upgrade to clawtel 0.1.4+ |
## Security footprint (tell the user)
Before enabling telemetry on a machine that holds sensitive conversations, share this with the user:
- **Reads only** `created_at`, `model`, `prompt_tokens`, `completion_tokens` from `nodes`
- **Never reads** `content`, `bucket`, `project`, or `agent_name`
- **Sends only** the fields in the heartbeat payload (claw_id, window_start/end, model, input/output tokens, message_count) โ no prompts, no responses, no paths, no hostnames
- **Read-only** SQLite connection (`?mode=ro`)
- **No network** without `CLAW_INGEST_KEY`
- Entire implementation is one file: `main.go`, ~390 lines. `send()` is the network contract, `readRows()` is the SQL. Both are auditable in one sitting.
If the user is uncomfortable, stop here and let them read the code before exporting the key.
don't have the plugin yet? install it then click "run inline in claude" again.
get clawtel reporting token usage from any project calling anthropic (sdk, claude code, openclaw, staffchief-style agents) to the claw.tech leaderboard.
clawtel is a single go binary that reads aggregate token counts from a local tapes sqlite database and sends heartbeats to https://ingest.claw.tech/v1/heartbeat. it never reads prompt or response content, only created_at, model, prompt_tokens, completion_tokens from the nodes table.
use this skill when you have an anthropic api workload (direct sdk call, claude code, or agent framework) running with tapes in the request path and need to wire token telemetry to the claw.tech leaderboard. covers binary install, env var setup, tapes wiring (including the openclaw override gotcha), systemd service config, verification, and debugging. skip this if your workload doesn't call anthropic or if you don't have a claw.tech ingest key yet.
required binaries and tools:
tapes already installed and running as a proxy in front of anthropic. if not present, install via curl -fsSL https://download.tapes.dev/install | bash then run tapes init.sqlite3 cli (for verification queries)required environment variables:
CLAW_INGEST_KEY (required to send heartbeats, e.g. ik_abc123...). register your claw at https://claw.tech and copy the ingest key shown once at creation. without this, clawtel exits silently.CLAW_ID (your claw name, e.g. my-claw-name)TAPES_DB (optional; path to tapes.sqlite). if unset, clawtel searches in order: .mb/tapes/tapes.sqlite (openclaw-in-a-box), then ~/.tapes/tapes.sqlite (standalone).external connections:
http://localhost:8080)https://ingest.claw.tech/v1/heartbeat, outbound https, port 443)anthropic-calling workload:
@anthropic-ai/sdkanthropic sdkapi.anthropic.comclawtel only sees usage that tapes has recorded. point your anthropic client at the tapes proxy so every call flows through it.
for sdk users (node/python):
export ANTHROPIC_BASE_URL="http://localhost:8080"
tapes start # starts proxy + api, writes to ~/.tapes/tapes.sqlite
no code changes needed. the sdk reads ANTHROPIC_BASE_URL automatically and routes to the proxy.
for claude code:
set the env var before launching the cli:
export ANTHROPIC_BASE_URL="http://localhost:8080"
claude
for openclaw agents (clawchief, staffchief, openclaw-in-a-box):
input: none
output: ~/.openclaw/openclaw.json updated with models.providers.anthropic.baseUrl
the env var alone does not work with openclaw. openclaw instantiates its anthropic client with baseURL: model.baseUrl, which overrides the sdk's normal readEnv("ANTHROPIC_BASE_URL") fallback. you'll see heartbeats sending with model="", input_tokens=0, output_tokens=0 forever.
step 1a: edit ~/.openclaw/openclaw.json:
{
"models": {
"providers": {
"anthropic": {
"baseUrl": "http://localhost:8080",
"models": [
{ "id": "claude-opus-4-6", "name": "Claude Opus 4.6" },
{ "id": "claude-sonnet-4-6", "name": "Claude Sonnet 4.6" },
{ "id": "claude-haiku-4-5", "name": "Claude Haiku 4.5" }
]
}
}
}
}
step 1b: restart the openclaw gateway:
pkill -f openclaw-gateway
# or use systemctl if it's a service
step 1c: verify the gateway is routing through tapes (input: none, output: network socket list):
ss -tnp | awk -v pid="$(pgrep -f openclaw-gateway | head -1)" '$0 ~ "pid="pid'
expect a line with peer address 127.0.0.1:8080. if you see 160.79.*.* or a cloudflare ip, tapes is still bypassed.
step 1d: confirm tapes is capturing rows (input: none, output: row count and max timestamp):
sqlite3 ~/.tapes/tapes.sqlite \
'SELECT count(*), max(created_at) FROM nodes;'
if the count is zero after making a call, tapes isn't in the request path. recheck ANTHROPIC_BASE_URL in the process actually running the agent (and for openclaw, the models.providers.anthropic.baseUrl config above).
pick one of two paths: verified install (pinned release, checksum) or convenience one-liner.
option a: verified install (pinned release + checksum)
input: version tag (e.g. v0.1.9), platform (e.g. darwin_arm64, linux_amd64, linux_arm64)
output: clawtel binary on $PATH, verified against sha256
VERSION=v0.1.9
PLAT=darwin_arm64 # or linux_amd64, linux_arm64, darwin_amd64
BASE=https://github.com/bdougie/clawtel/releases/download/${VERSION}
curl -fsSLO "${BASE}/clawtel_${PLAT}.tar.gz"
curl -fsSLO "${BASE}/checksums.txt"
# verify. sha256sum on linux, shasum -a 256 on macos.
grep "clawtel_${PLAT}.tar.gz" checksums.txt | sha256sum -c - # or: shasum -a 256 -c -
tar -xzf "clawtel_${PLAT}.tar.gz"
install -m 755 clawtel "$HOME/.local/bin/clawtel" # or /usr/local/bin with sudo
option b: convenience one-liner
input: none (script detects latest release and platform)
output: clawtel binary in /usr/local/bin or $CLAWTEL_INSTALL_DIR
the install script is auditable: https://raw.githubusercontent.com/bdougie/clawtel/main/scripts/install.sh
curl -fsSL https://raw.githubusercontent.com/bdougie/clawtel/main/scripts/install.sh | bash
or to a user directory without sudo:
CLAWTEL_INSTALL_DIR="$HOME/.local/bin" \
curl -fsSL https://raw.githubusercontent.com/bdougie/clawtel/main/scripts/install.sh | bash
step 2a: verify install (input: none, output: version string):
clawtel --version
should print a version string matching the release you intended.
input: your claw name and ingest key (from https://claw.tech), optional custom tapes db path output: env vars exported in shell or persistent config file
export CLAW_ID="your-claw-name"
export CLAW_INGEST_KEY="ik_..."
# only if tapes.sqlite lives somewhere non-standard:
export TAPES_DB="/custom/path/tapes.sqlite"
put these in ~/.zshrc, ~/.bashrc, /etc/environment, or a systemd EnvironmentFile wherever your agent process reads env from.
database path resolution order (if TAPES_DB unset):
.mb/tapes/tapes.sqlite (openclaw-in-a-box layout)~/.tapes/tapes.sqlite (standalone tapes install)if clawtel exits silently on start, CLAW_INGEST_KEY is not exported in this shell or service scope.
input: env vars set (step 3), tapes running (step 1), tapes.sqlite writable by current user output: startup log showing db path, cursor location, claw id, polling interval
clawtel
expected startup log:
clawtel: clawtel 0.1.x
clawtel: db: /home/you/.tapes/tapes.sqlite
clawtel: cursor: /home/you/.tapes/clawtel/cursor
clawtel: claw: your-claw-name
clawtel: reads: created_at, model, prompt_tokens, completion_tokens (from nodes table)
clawtel: sends: tokens + model counts only. no prompts. no responses.
clawtel: NOTE: nodes table has column "content" , clawtel does NOT read it
clawtel: polling every 1h
any NOTE: lines listing sensitive columns are good. they confirm clawtel sees those columns and deliberately ignores them.
input: clawtel binary installed, env vars ready, systemd available on target machine
output: /etc/systemd/system/clawtel.service, /etc/clawtel.env, systemd unit enabled and running
create /etc/systemd/system/clawtel.service:
[Unit]
Description=clawtel , token telemetry for claw.tech
After=network-online.target
Wants=network-online.target
[Service]
Type=simple
User=root
EnvironmentFile=/etc/clawtel.env
ExecStart=/usr/local/bin/clawtel
Restart=on-failure
RestartSec=10
[Install]
WantedBy=multi-user.target
create /etc/clawtel.env with 0600 permissions:
CLAW_ID=your-claw-name
CLAW_INGEST_KEY=ik_...
TAPES_DB=/root/.tapes/tapes.sqlite
then:
chmod 600 /etc/clawtel.env
systemctl daemon-reload
systemctl enable --now clawtel
journalctl -u clawtel -f
on the same machine as an agent like clawchief or staffchief, TAPES_DB should point at the same sqlite file the agent's tapes proxy is writing to.
input: running clawtel (foreground or systemd), agent making real model calls
output: log line showing sent heartbeat with non-zero token counts, updated claw.tech leaderboard
step 6a: trigger a real model call in your agent (send a message, run claude once, let the cron fire).
step 6b: wait up to one full poll interval (currently 1 hour) or stop/start clawtel to force an immediate send:
systemctl restart clawtel
step 6c: check logs for a sent heartbeat line with non-zero input_tokens/output_tokens:
journalctl -u clawtel -f # systemd service
# or check foreground logs if running in terminal
step 6d: load your profile on claw.tech. the leaderboard row should update within a minute of a successful heartbeat.
if CLAW_INGEST_KEY is unset: clawtel exits silently on start. no network calls are made. fix: export the key in the same shell or systemd scope where clawtel runs. verify with systemctl show clawtel -p Environment or env | grep CLAW_.
if tapes is not in the request path (agent still talks to api.anthropic.com directly): heartbeats send but all token counts are zero. fix: confirm ANTHROPIC_BASE_URL is set in the agent process environment. for openclaw agents, also set models.providers.anthropic.baseUrl = "http://localhost:8080" in ~/.openclaw/openclaw.json.
if tapes.sqlite is missing required columns: clawtel fails on startup with assertSchema failed. fix: confirm with sqlite3 $TAPES_DB '.schema nodes' that created_at, model, prompt_tokens, completion_tokens all exist. if not, tapes version is too old or the db file is wrong. repoint TAPES_DB or upgrade tapes.
if the ingest endpoint returns 401: the key is wrong or has been rotated. fix: regenerate the key on claw.tech and update CLAW_INGEST_KEY.
if leaderboard shows offline despite running clawtel: two consecutive missed heartbeats (more than 2x the poll interval). fix: check journalctl -u clawtel for network errors. confirm outbound https to ingest.claw.tech:443 is open (no firewall blocks). if cursor is stuck, see outcome signal section.
if running clawtel < 0.1.4 and cursor seems stuck: the cursor is written as rfc3339nano but tapes stores timestamps with a space separator. sqlite string comparison fails so the poll returns zero rows forever (see issue #4). fix: upgrade to clawtel 0.1.4 or later.
if cursor directory is not writable by the clawtel user: clawtel can't persist its position and re-processes the same rows on every poll. fix: chown the ~/.tapes/clawtel/ directory to the service user (e.g. chown root ~/.tapes/clawtel/).
on successful startup (foreground):
clawtel: clawtel X.X.X, listing db path, cursor location, claw id, and polling intervalFATAL log lineson successful heartbeat send:
journalctl -u clawtel -f (or foreground log) shows a line matching sent heartbeat with fields: claw_id=..., window_start=..., window_end=..., input_tokens=N (non-zero), output_tokens=M (non-zero), message_count=K, models={...}200 OKcreated_at timestamp from the nodes tableleaderboard update:
file locations:
/usr/local/bin/clawtel (or $HOME/.local/bin/clawtel if using --user install)~/.tapes/tapes.sqlite (or $TAPES_DB if overridden)~/.tapes/clawtel/cursor (or $(dirname $TAPES_DB)/clawtel/cursor)/etc/systemd/system/clawtel.service/etc/clawtel.envjournalctl -u clawteluser knows it worked when:
foreground test passes: clawtel starts without errors, prints the startup summary, and you see the cursor file created at ~/.tapes/clawtel/cursor.
heartbeat log appears: after triggering a real model call and waiting (or restarting clawtel to force an immediate poll), journalctl -u clawtel -f (or foreground log) shows a sent heartbeat line with non-zero input_tokens and output_tokens. example:
clawtel: sent heartbeat claw_id=my-claw input_tokens=1234 output_tokens=567 message_count=3 models={claude-3-sonnet: 1}
leaderboard updates: load your profile on claw.tech. your token counts and last-seen timestamp change within a minute of the heartbeat log line.
systemd service is stable: if running as a service, systemctl status clawtel shows active (running), and journalctl -u clawtel --since "5 min ago" shows a recent sent heartbeat line (or polling... if no new rows have been added).
no auth/permission errors: logs don't show 401, 403, permission denied, or CLAW_INGEST_KEY warnings. env check passes: systemctl show clawtel -p Environment | grep CLAW_INGEST_KEY.