feat: 用 react-map-gl + MapLibre 替换 react-simple-maps 实现 2D 地图

- 替换 react-simple-maps/d3-geo/topojson-client 为 react-map-gl + maplibre-gl
- 使用 CARTO dark-matter 免费暗色瓦片,自带国家/城市名标注
- 基于 locale 动态切换地图标注语言(name:zh / name_en)
- MapLibre 原生 heatmap + circle 双层渲染替代 SVG 热力图
- 提取 MapPopup 组件,配合 react-map-gl Popup 实现点击弹窗
- continent page 改为 dynamic import (ssr: false)
- dev 模式去掉 Turbopack 以兼容 maplibre-gl
- 删除 heatmap-layer.tsx 和 react-simple-maps 类型声明
This commit is contained in:
richarjiang
2026-03-13 14:46:23 +08:00
parent 9e30771180
commit e79d721615
23 changed files with 1215 additions and 494 deletions

View File

@@ -4,16 +4,21 @@ description: |
Opt-in reporter for the OpenClaw global claw heatmap.
On first use, ASKS the user for consent before registering.
Sends heartbeats (platform + model only) and generic task summaries to https://kymr.top/.
Heartbeats are sent automatically via hooks at session start — no LLM action needed.
Task reports are sent automatically via hooks when the agent stops responding.
Heartbeats are sent at session start and renewed via a PostToolUse hook, rate-limited
to at most once per 3 minutes — most invocations exit immediately after a timestamp check.
The PostToolUse hook does NOT read or send any tool names, arguments, or results.
Task reports are sent automatically via a Stop hook when the agent stops responding.
Data collected: user-chosen claw name, OS platform, model name, generic task category.
Credentials: the server returns an apiKey on registration, which is stored locally
in ~/.openclaw/config.json and sent as a Bearer token in subsequent requests.
No file paths, code, or project-specific data is ever sent.
No file paths, code, tool names, or project-specific data is ever sent.
requirements:
network: true
writeFiles:
- "~/.openclaw/config.json"
- "~/.openclaw/scripts/heartbeat.sh"
- "~/.openclaw/scripts/task-report.sh"
- "~/.openclaw/.last_heartbeat"
binaries:
- curl
- python3
@@ -26,29 +31,34 @@ hooks:
SessionStart:
- hooks:
- type: command
command: "if [ -f ~/.openclaw/config.json ]; then KEY=$(python3 -c \"import json; print(json.load(open('$HOME/.openclaw/config.json'))['apiKey'])\") && curl -s -o /dev/null --max-time 5 -X POST 'https://kymr.top/api/v1/heartbeat' -H 'Content-Type: application/json' -H \"Authorization: Bearer $KEY\" -d \"{\\\"platform\\\":\\\"$(uname -s | tr '[:upper:]' '[:lower:]')\\\",\\\"model\\\":\\\"${CLAUDE_MODEL:-unknown}\\\"}\"; fi"
command: "bash $HOME/.openclaw/scripts/heartbeat.sh 2>/dev/null || true"
timeout: 10
once: true
PostToolUse:
- hooks:
- type: command
command: "bash $HOME/.openclaw/scripts/heartbeat.sh 2>/dev/null || true"
timeout: 5
Stop:
- hooks:
- type: command
command: "if [ -f ~/.openclaw/config.json ]; then KEY=$(python3 -c \"import json; print(json.load(open('$HOME/.openclaw/config.json'))['apiKey'])\") && curl -s -o /dev/null --max-time 5 -X POST 'https://kymr.top/api/v1/task' -H 'Content-Type: application/json' -H \"Authorization: Bearer $KEY\" -d \"{\\\"summary\\\":\\\"Completed a task\\\",\\\"model\\\":\\\"${CLAUDE_MODEL:-unknown}\\\"}\"; fi"
command: "bash $HOME/.openclaw/scripts/task-report.sh 2>/dev/null || true"
timeout: 10
---
# OpenClaw Reporter
This skill lets you participate in the OpenClaw global claw activity heatmap. It reports **anonymous, non-identifying** activity data to visualize worldwide AI agent usage.
This skill lets you participate in the OpenClaw global claw activity heatmap. It reports **minimal, non-code** activity data to visualize worldwide AI agent usage. Note: as with any HTTPS request, the server can observe your IP address.
## How It Works
- **Heartbeat**: Sent automatically at session start via a `SessionStart` hook — no manual action needed.
- **Heartbeat**: Sent at session start (`SessionStart` hook) and renewed periodically (`PostToolUse` hook, rate-limited to once per 3 minutes). The `PostToolUse` hook does **not** read tool names, arguments, or results — it only triggers the same timestamp-checked heartbeat script. Most invocations exit immediately after reading a single timestamp file.
- **Task report**: Sent automatically when the agent finishes responding via a `Stop` hook.
- **Registration**: One-time setup requiring explicit user consent (see below).
- **Hooks**: Hooks are registered in the skill frontmatter and activate when the skill is loaded. No system-level files are modified.
## Data Disclosure
This skill sends the following data to `https://kymr.top/`:
This skill sends the following data to `https://kymr.top/` (the OpenClaw Market production server; server-side source code is in this repository under `app/api/v1/`):
| Data Field | Example | Purpose |
|---|---|---|
@@ -57,13 +67,15 @@ This skill sends the following data to `https://kymr.top/`:
| Model | `claude-sonnet-4-6` | Model usage stats |
| Task summary | `"Completed a task"` | Generic activity indicator |
**Stored locally:** Registration returns an `apiKey` from the server, saved in `~/.openclaw/config.json` with `chmod 600` (owner-only access). This token is sent as `Authorization: Bearer` header in heartbeat and task report requests. The endpoint URL (`https://kymr.top/`) is hardcoded in hooks — not read from the config file — so a modified config cannot redirect telemetry. The `CLAUDE_MODEL` environment variable is read (if available) to identify the active model — it falls back to `"unknown"` if not set.
**Stored locally:** Registration returns an `apiKey` from the server, saved in `~/.openclaw/config.json` with `chmod 600` (owner-only access). This token is sent as `Authorization: Bearer` header in heartbeat and task report requests. The endpoint URL (`https://kymr.top/`) is hardcoded in hook scripts — not read from the config file — so a modified config cannot redirect telemetry. The `CLAUDE_MODEL` environment variable is read (if available) to identify the active model — it falls back to `"unknown"` if not set.
**Never sent:** system usernames, file paths, code snippets, project names, secrets, IP-derived geolocation beyond what the server naturally receives.
**Visible to the server:** Your IP address is visible on every HTTPS request (this is inherent to any network call and not specific to this skill). The server uses IP for approximate geolocation on the heatmap — no more precise than city-level.
**Never sent:** system usernames, file paths, code snippets, project names, tool names, tool arguments, tool results, or secrets.
## Configuration
Config is stored at `~/.openclaw/config.json` (permissions `600`). The hooks check for this file before sending any requests — if it doesn't exist, nothing is sent. The endpoint is hardcoded to `https://kymr.top/` in the hook commands and is **not** configurable via the config file.
Config is stored at `~/.openclaw/config.json` (permissions `600`). Hook scripts live at `~/.openclaw/scripts/`. The hooks check for the config file before sending any requests — if it doesn't exist, nothing is sent. The endpoint is hardcoded to `https://kymr.top/` in the scripts and is **not** configurable via the config file.
## First-Time Setup (Requires User Consent)
@@ -73,7 +85,7 @@ If `~/.openclaw/config.json` does not exist, follow these steps:
Use the AskUserQuestion tool:
- Question: "OpenClaw Reporter wants to register a claw name and send activity heartbeats to https://kymr.top/ for the global heatmap. The server will issue an API key stored locally at ~/.openclaw/config.json. No file paths, code, or project data is sent. Allow?"
- Question: "OpenClaw Reporter wants to register a claw name and send periodic heartbeats (once per 3 min, platform + model only) to https://kymr.top/ for the global heatmap. The server will issue an API key stored locally at ~/.openclaw/config.json. No file paths, code, tool names, or project data is sent. Allow?"
- Options: "Yes, register" / "No, skip"
**If the user selects "No, skip":** do NOT register and do NOT ask again in this session. Stop here.
@@ -88,17 +100,22 @@ If the user consented, use AskUserQuestion again:
Use whatever the user provides as the claw name.
### Step 3: Register
### Step 3: Register and install hooks
After receiving the name (stored as `CLAW_NAME`):
After receiving the name (stored as `CLAW_NAME`), run Step 3a and Step 3b in sequence.
#### Step 3a: Register and save config
```bash
# Create config directory
mkdir -p ~/.openclaw
# --- OpenClaw Registration ---
set -e
OPENCLAW_ENDPOINT="https://kymr.top"
CLAW_NAME="USER_CHOSEN_NAME_HERE"
export CLAW_NAME="USER_CHOSEN_NAME_HERE"
mkdir -p ~/.openclaw/scripts
# Register with the server
RESPONSE=$(curl -s -X POST "$OPENCLAW_ENDPOINT/api/v1/register" \
-H "Content-Type: application/json" \
-d "{
@@ -107,38 +124,117 @@ RESPONSE=$(curl -s -X POST "$OPENCLAW_ENDPOINT/api/v1/register" \
\"model\": \"$(echo $CLAUDE_MODEL 2>/dev/null || echo 'unknown')\"
}")
# Save config (no endpoint stored — hooks use a hardcoded URL)
# Save config — pipe response via stdin to avoid shell injection
# Creates ~/.openclaw/config.json with chmod 600 (owner-only access)
echo "$RESPONSE" | python3 -c "
import json, sys, os
data = json.load(sys.stdin)
data = json.loads(sys.stdin.read())
config = {
'clawId': data.get('clawId', ''),
'apiKey': data.get('apiKey', ''),
'name': '$CLAW_NAME'
'name': os.environ.get('CLAW_NAME', '')
}
path = os.path.expanduser('~/.openclaw/config.json')
with open(path, 'w') as f:
json.dump(config, f, indent=2)
os.chmod(path, 0o600)
print('Registered as:', '$CLAW_NAME')
print('Registered as:', config['name'])
"
```
## Opt-Out / Unregister
To stop all OpenClaw reporting, delete the config file:
#### Step 3b: Deploy hook scripts
```bash
rm -f ~/.openclaw/config.json
rmdir ~/.openclaw 2>/dev/null
# --- Deploy heartbeat script (rate-limited to once per 180s) ---
cat > ~/.openclaw/scripts/heartbeat.sh << 'HOOKEOF'
#!/bin/bash
# OpenClaw heartbeat — rate-limited to once per 180 seconds.
# No tool names, arguments, or results are read or sent.
CONFIG="$HOME/.openclaw/config.json"
[ -f "$CONFIG" ] || exit 0
LAST_FILE="$HOME/.openclaw/.last_heartbeat"
NOW=$(date +%s)
if [ -f "$LAST_FILE" ]; then
LAST=$(cat "$LAST_FILE" 2>/dev/null || echo 0)
[ $((NOW - LAST)) -lt 180 ] && exit 0
fi
KEY=$(python3 -c "import json; print(json.load(open('$CONFIG'))['apiKey'])" 2>/dev/null) || exit 0
[ -z "$KEY" ] && exit 0
curl -s -o /dev/null --max-time 5 -X POST 'https://kymr.top/api/v1/heartbeat' \
-H 'Content-Type: application/json' \
-H "Authorization: Bearer $KEY" \
-d "{\"platform\":\"$(uname -s | tr '[:upper:]' '[:lower:]')\",\"model\":\"${CLAUDE_MODEL:-unknown}\"}" \
2>/dev/null && echo "$NOW" > "$LAST_FILE" || true
HOOKEOF
chmod +x ~/.openclaw/scripts/heartbeat.sh
# --- Deploy task report script ---
cat > ~/.openclaw/scripts/task-report.sh << 'HOOKEOF'
#!/bin/bash
CONFIG="$HOME/.openclaw/config.json"
[ -f "$CONFIG" ] || exit 0
KEY=$(python3 -c "import json; print(json.load(open('$CONFIG'))['apiKey'])" 2>/dev/null) || exit 0
[ -z "$KEY" ] && exit 0
curl -s -o /dev/null --max-time 5 -X POST 'https://kymr.top/api/v1/task' \
-H 'Content-Type: application/json' \
-H "Authorization: Bearer $KEY" \
-d "{\"summary\":\"Completed a task\",\"model\":\"${CLAUDE_MODEL:-unknown}\"}" 2>/dev/null || true
HOOKEOF
chmod +x ~/.openclaw/scripts/task-report.sh
echo "--- OpenClaw setup complete ---"
```
After deletion, no heartbeats or task reports will be sent. The hooks silently skip when the config file is absent. The skill will ask for consent again if re-enabled.
## Hook Verification (Auto-Repair)
**Every time this skill is loaded**, if `~/.openclaw/config.json` exists, verify that hook scripts are present. Run this check silently:
1. Check `~/.openclaw/scripts/heartbeat.sh` exists and is executable
2. Check `~/.openclaw/scripts/task-report.sh` exists and is executable
If **any** check fails, re-run Step 3b from the registration above to redeploy the scripts. Do this silently without asking the user.
Verification command (run silently on skill load when config exists):
```bash
# Quick hook health check — re-deploy scripts if missing
if [ -f ~/.openclaw/config.json ]; then
NEEDS_REPAIR=false
[ -x ~/.openclaw/scripts/heartbeat.sh ] || NEEDS_REPAIR=true
[ -x ~/.openclaw/scripts/task-report.sh ] || NEEDS_REPAIR=true
if [ "$NEEDS_REPAIR" = true ]; then
echo "OpenClaw hook scripts need repair — reinstalling..."
# (re-run Step 3b from registration above)
fi
fi
```
If repair is needed, re-run Step 3b (Deploy hook scripts) from the registration above.
## Opt-Out / Unregister
To stop all OpenClaw reporting and remove hooks:
```bash
# Remove scripts, config, and timestamp
rm -f ~/.openclaw/scripts/heartbeat.sh ~/.openclaw/scripts/task-report.sh
rmdir ~/.openclaw/scripts 2>/dev/null
rm -f ~/.openclaw/config.json ~/.openclaw/.last_heartbeat
rmdir ~/.openclaw 2>/dev/null
echo "OpenClaw fully uninstalled."
```
After deletion, no heartbeats or task reports will be sent. The skill will ask for consent again if re-enabled.
## Behavior Rules
1. **User consent is REQUIRED** before first registration — never register silently
2. If the user declines registration, respect the decision and do not ask again in the same session
3. Hooks run automatically — do NOT manually execute heartbeat or task report commands
3. Hooks run automatically via skill frontmatter — do NOT manually execute heartbeat or task report commands
4. If any OpenClaw request fails, continue normally — never interrupt the user's workflow
5. Never include identifying information in task summaries
6. **On skill load**: if `~/.openclaw/config.json` exists, silently verify hook scripts are present and redeploy if missing
7. **Idempotent**: running script deployment multiple times is safe
8. **No system-level modifications**: never write to `~/.claude/settings.json` or any file outside `~/.openclaw/`