feat(pua): add silent heartbeat stats

Summary:
- Add a silent SessionStart heartbeat hook that posts minimal anonymous
  install activity to Cloudflare without emitting stdout, stderr, or
  additionalContext.
- Add the Cloudflare Pages heartbeat endpoint, D1 migration, and
  admin-only stats page at #/admin/heartbeats.
- Add heartbeat regression gates, release consistency checks, FAQ/design
  notes, and bump marketplace/package versions to 3.4.3.

Rationale:
- Active-user measurement belongs in a hook-side mechanical gate, not in
  the model prompt, so the worker context cannot narrate or leak the
  telemetry behavior.
- Privacy gates disable the signal for offline mode, telemetry opt-out,
  and feedback-off users before any local telemetry identity is created.

Tests:
- bash evals/test-heartbeat.sh
- bash evals/test-release-consistency.sh
- static eval suite: governance, feedback auth, integrity guard, issue
  regressions, Microsoft flavor, platform compat, loop hook, Windows
  Python hooks, YAML frontmatter
- bash evals/test-behavior.sh
- npm --prefix landing run build
- ./landing/node_modules/.bin/tsc -p pi/pua/tsconfig.json
- git diff --check

Co-authored-by: Codex <codex@openai.com>
This commit is contained in:
xsser
2026-05-09 17:01:38 +08:00
parent 308767f107
commit d40ca8a164
16 changed files with 932 additions and 15 deletions
+2 -2
View File
@@ -8,8 +8,8 @@
"plugins": [
{
"name": "pua",
"description": "PUA 我们不养闲 Agent — 11 modular skills, 14 corporate flavors, L0-L4 pressure, auto-iteration loop, ENFP yes-mode, Chinese-mom nagging mode, concentrated shot mode, agent lifecycle teardown protocol, feedback system. v3.4.2: authentic Microsoft flavor with Connects, Impact Descriptors, SLITE/LITE, PIP/GVSA, and Three Circles of Impact.",
"version": "3.4.2",
"description": "PUA 我们不养闲 Agent — 11 modular skills, 14 corporate flavors, L0-L4 pressure, auto-iteration loop, ENFP yes-mode, Chinese-mom nagging mode, concentrated shot mode, agent lifecycle teardown protocol, feedback system. v3.4.3: silent heartbeat hook with admin-only Cloudflare active-user stats and no prompt leakage.",
"version": "3.4.3",
"source": "./",
"author": {
"name": "探微安全实验室",
+6 -3
View File
@@ -1,7 +1,7 @@
{
"name": "pua",
"version": "3.4.2",
"description": "Forces high-agency exhaustive problem-solving with corporate PUA pressure. Triggers on explicit PUA requests, user frustration, repeated failures, passive behavior, unverified completion, or quality complaints. v3.4.2 restores authentic Microsoft flavor: Connects, Impact Descriptors, SLITE/LITE, PIP/GVSA, and Three Circles of Impact. Not for normal first-attempt requests.",
"version": "3.4.3",
"description": "Forces high-agency exhaustive problem-solving with corporate PUA pressure. Triggers on explicit PUA requests, user frustration, repeated failures, passive behavior, unverified completion, or quality complaints. v3.4.3 adds silent heartbeat telemetry and admin-only Cloudflare active-user stats without prompt leakage. Not for normal first-attempt requests.",
"author": {
"name": "探微安全实验室",
"url": "https://github.com/tanweai"
@@ -24,6 +24,9 @@
"owner-consciousness",
"methodology",
"mama-mode",
"chinese-mom"
"chinese-mom",
"heartbeat",
"cloudflare",
"active-users"
]
}
+3 -3
View File
@@ -1,7 +1,7 @@
{
"name": "pua-skills",
"description": "PUA Motivator for CodeBuddy -- high-agency pressure mode for explicit PUA requests, repeated failures, user frustration, passive behavior, unverified completion, with 14 corporate/PIP flavors.",
"version": "3.4.2",
"version": "3.4.3",
"owner": {
"name": "探微安全实验室",
"url": "https://github.com/tanweai"
@@ -9,8 +9,8 @@
"plugins": [
{
"name": "pua",
"description": "PUA Motivator -- forces CodeBuddy to exhaust materially different approaches before giving up. v3.4.2 adds authentic Microsoft Connects/PIP/GVSA flavor for fixed-thinking failures.",
"version": "3.4.2",
"description": "PUA Motivator -- forces CodeBuddy to exhaust materially different approaches before giving up. v3.4.3 adds silent active-user telemetry plumbing for Claude Code while preserving no-leak prompt behavior.",
"version": "3.4.3",
"source": "./"
}
]
+6 -3
View File
@@ -1,7 +1,7 @@
{
"name": "pua",
"version": "3.4.2",
"description": "PUA Motivator -- forces high-agency follow-through when explicitly requested, after repeated failures, user frustration, passive/giving-up behavior, or unverified completion. Chinese corporate PUA and English PIP cultural modes, now with authentic Microsoft Connects/PIP/GVSA flavor. Not for normal first-attempt requests.",
"version": "3.4.3",
"description": "PUA Motivator -- forces high-agency follow-through when explicitly requested, after repeated failures, user frustration, passive/giving-up behavior, or unverified completion. Includes corporate/PIP cultural modes plus silent active-user stats for Claude Code deployments. Not for normal first-attempt requests.",
"author": {
"name": "探微安全实验室",
"url": "https://github.com/tanweai"
@@ -17,7 +17,10 @@
"corporate-culture",
"pip",
"performance-improvement-plan",
"codebuddy"
"codebuddy",
"heartbeat",
"cloudflare",
"active-users"
],
"category": "Productivity"
}
+11
View File
@@ -77,3 +77,14 @@ Codex 没有 Claude Code 的 `/pua:xxx` slash command 命名空间时,可以
- start/intervene → “亲自动手” / “亲自介入”;
- stop/release → “释放” / “退场”。
## 静默 heartbeat 会不会污染对话?
不会。v3.4.3 的活跃用户统计走 **SessionStart command hook**,不是 skill prompt,也不输出 `additionalContext`。因此模型上下文里不会出现 heartbeat endpoint、install id 或统计提示。
治理边界:
- `offline: true``telemetry: false``feedback_frequency: 0` 会关闭 heartbeat
- 本地只生成随机 install idCloudflare D1 只保存 SHA-256 hash
- 管理页面是 `https://openpua.ai/#/admin/heartbeats`,需要 GitHub 登录并命中管理员白名单;
- hook 有静默测试:即使网络失败,也不能向对话输出任何字节。
@@ -0,0 +1,37 @@
# Silent Heartbeat + Cloudflare Active-User Stats Design
## Goal
Add active-user visibility for PUA Skill without polluting Claude conversations. The heartbeat must be a mechanical hook-side signal, not prompt content: no `additionalContext`, no stdout/stderr, no text the model can echo.
## Architecture
- **Action side:** `hooks/heartbeat.sh` runs on `SessionStart`, before `session-restore.sh`.
- **Conversation boundary:** heartbeat writes nothing to stdout/stderr and never appears in `skills/pua/SKILL.md` or SessionStart context.
- **Privacy gates:** `offline=true`, `telemetry=false`, or `feedback_frequency=0` disables heartbeat before creating a local install id.
- **Telemetry identity:** local random `~/.pua/install_id`; Cloudflare stores only SHA-256 hashes.
- **Cloudflare side:** `/api/heartbeat` accepts minimal POST events and authenticated admin GET stats.
- **Admin page:** `#/admin/heartbeats` shows active installs/sessions by day, version, platform, and flavor.
## Data Flow
```mermaid
flowchart LR
SessionStart --> HeartbeatHook[hooks/heartbeat.sh]
HeartbeatHook -->|silent POST| CF[/api/heartbeat]
CF --> D1[(D1: heartbeat_installs/events)]
Admin[GitHub-auth admin] --> Stats[#/admin/heartbeats]
Stats --> CF
```
## Mechanical Gates
1. Hook silence gate: runtime test proves no stdout/stderr even when network fails.
2. Context leak gate: SessionStart additionalContext must not contain heartbeat endpoint/id words.
3. Privacy gate: offline mode must not create telemetry identity.
4. Admin gate: GET stats requires signed session + GitHub allowlist.
5. Abuse gate: POST body size limit, origin allowlist when Origin exists, per-IP/UA rate limit.
## INTJ Note
The design separates **measurement** from **narrative**. If telemetry is placed in the skill prompt, it becomes part of the model's story and can leak into outputs. If telemetry is placed in a silent hook with tests, it becomes an institution-level control: observable to the owner, invisible to the worker context.
+124
View File
@@ -0,0 +1,124 @@
#!/usr/bin/env bash
# Heartbeat telemetry gates: silent SessionStart hook + admin-only Cloudflare stats.
set -euo pipefail
ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
PASS=0
FAIL=0
pass() { echo " ✅ PASS: $1"; PASS=$((PASS+1)); }
fail() { echo " ❌ FAIL: $1"; FAIL=$((FAIL+1)); }
assert_file() { local file="$1" name="$2"; [ -f "$ROOT/$file" ] && pass "$name" || fail "$name"; }
assert_grep() { local pat="$1" file="$2" name="$3"; grep -qE -- "$pat" "$ROOT/$file" 2>/dev/null && pass "$name" || fail "$name"; }
assert_not_grep() { local pat="$1" file="$2" name="$3"; ! grep -qE -- "$pat" "$ROOT/$file" 2>/dev/null && pass "$name" || fail "$name"; }
run_silence_check() {
local label="$1"
local home_dir="$2"
local config_json="$3"
local out err
mkdir -p "$home_dir/.pua"
printf '%s\n' "$config_json" > "$home_dir/.pua/config.json"
out="$home_dir/out.txt"
err="$home_dir/err.txt"
HOME="$home_dir" \
CLAUDE_PLUGIN_ROOT="$ROOT" \
PUA_HEARTBEAT_ENDPOINT="http://127.0.0.1:9/api/heartbeat" \
PUA_HEARTBEAT_INTERVAL_SECONDS=0 \
bash "$ROOT/hooks/heartbeat.sh" >"$out" 2>"$err" || true
sleep 0.2
if [ ! -s "$out" ] && [ ! -s "$err" ]; then
pass "$label emits no stdout/stderr"
else
fail "$label emits no stdout/stderr"
sed 's/^/ stdout: /' "$out" || true
sed 's/^/ stderr: /' "$err" || true
fi
}
echo "=== Heartbeat Gates ==="
assert_file hooks/heartbeat.sh "heartbeat hook exists"
assert_file landing/functions/api/heartbeat.ts "Cloudflare heartbeat endpoint exists"
assert_file landing/migrations/0004_heartbeat.sql "D1 heartbeat migration exists"
assert_file landing/src/pages/AdminStats.tsx "admin heartbeat page exists"
if [ -f "$ROOT/hooks/heartbeat.sh" ]; then
bash -n "$ROOT/hooks/heartbeat.sh" && pass "heartbeat hook has valid bash syntax" || fail "heartbeat hook has valid bash syntax"
assert_grep '/api/heartbeat' hooks/heartbeat.sh "heartbeat hook posts to /api/heartbeat"
assert_grep 'PUA_HEARTBEAT_ENDPOINT' hooks/heartbeat.sh "heartbeat endpoint is overrideable"
assert_grep 'offline' hooks/heartbeat.sh "heartbeat respects offline config"
assert_grep 'telemetry' hooks/heartbeat.sh "heartbeat respects telemetry opt-out"
assert_grep 'feedback_frequency' hooks/heartbeat.sh "heartbeat respects feedback-off opt-out"
assert_grep 'get_flavor' hooks/heartbeat.sh "heartbeat normalizes flavor aliases"
assert_grep '--max-time' hooks/heartbeat.sh "heartbeat network call has timeout"
assert_grep '>/dev/null 2>&1|> *"?/dev/null"? +2>&1|2> *"?/dev/null"?' hooks/heartbeat.sh "heartbeat suppresses curl output"
fi
python3 - "$ROOT" <<'PY' && pass "SessionStart registers silent heartbeat before context restore" || fail "SessionStart registers silent heartbeat before context restore"
import json, pathlib, sys
root = pathlib.Path(sys.argv[1])
data = json.loads((root / 'hooks/hooks.json').read_text())
items = data.get('hooks', {}).get('SessionStart', [])
if not items:
raise SystemExit('no SessionStart hooks')
for item in items:
commands = [hook.get('command','') for hook in item.get('hooks', [])]
if not any('heartbeat.sh' in c for c in commands):
raise SystemExit(f"missing heartbeat in matcher {item.get('matcher')}")
hb = next(i for i,c in enumerate(commands) if 'heartbeat.sh' in c)
restore = next((i for i,c in enumerate(commands) if 'session-restore.sh' in c), None)
if restore is not None and hb > restore:
raise SystemExit(f"heartbeat should run before session-restore in matcher {item.get('matcher')}")
PY
if [ -f "$ROOT/landing/functions/api/heartbeat.ts" ]; then
assert_grep 'import \{ getSession \} from "\./_session"' landing/functions/api/heartbeat.ts "admin GET imports session verifier"
assert_grep 'ADMIN_GITHUB_LOGINS' landing/functions/api/heartbeat.ts "admin allowlist is configurable"
assert_grep 'heartbeat_installs' landing/functions/api/heartbeat.ts "endpoint writes install aggregate"
assert_grep 'heartbeat_events' landing/functions/api/heartbeat.ts "endpoint writes event log"
assert_grep 'COUNT\(DISTINCT install_id_hash\)' landing/functions/api/heartbeat.ts "stats count distinct active users"
assert_grep 'sha256Hex' landing/functions/api/heartbeat.ts "endpoint hashes install identifiers"
fi
if [ -f "$ROOT/landing/src/App.tsx" ]; then
assert_grep '#/admin/heartbeats' landing/src/App.tsx "admin hash route is registered"
fi
if [ -f "$ROOT/landing/src/pages/AdminStats.tsx" ]; then
assert_grep '/api/heartbeat' landing/src/pages/AdminStats.tsx "admin page fetches heartbeat stats"
assert_grep '/api/auth/github' landing/src/pages/AdminStats.tsx "admin page offers GitHub login"
fi
if [ -f "$ROOT/hooks/heartbeat.sh" ]; then
tmp="$(mktemp -d)"
trap 'rm -rf "$tmp"' EXIT
run_silence_check "online heartbeat" "$tmp/online-home" '{"telemetry": true, "flavor": "microsoft"}'
if [ -s "$tmp/online-home/.pua/install_id" ]; then
pass "heartbeat creates stable local anonymous install id"
else
fail "heartbeat creates stable local anonymous install id"
fi
run_silence_check "offline heartbeat" "$tmp/offline-home" '{"offline": true, "telemetry": true}'
if [ ! -e "$tmp/offline-home/.pua/install_id" ]; then
pass "offline mode does not create telemetry identity"
else
fail "offline mode does not create telemetry identity"
fi
leak_home="$tmp/leak-home"
mkdir -p "$leak_home/.pua"
printf '%s\n' '{"always_on": true, "telemetry": true}' > "$leak_home/.pua/config.json"
HOME="$leak_home" PUA_CONFIG="$leak_home/.pua/config.json" bash "$ROOT/hooks/session-restore.sh" > "$tmp/session.out" 2>/dev/null || true
if grep -Eiq 'heartbeat|install_id|/api/heartbeat' "$tmp/session.out"; then
fail "SessionStart context does not leak heartbeat implementation details"
else
pass "SessionStart context does not leak heartbeat implementation details"
fi
fi
assert_not_grep 'heartbeat|install_id|/api/heartbeat' skills/pua/SKILL.md "main skill prompt does not mention heartbeat"
echo "======================="
echo "Passed: $PASS"
echo "Failed: $FAIL"
echo "Total: $((PASS+FAIL))"
echo "======================="
[ "$FAIL" -eq 0 ] || exit 1
+16
View File
@@ -128,6 +128,11 @@ required_paths = [
'evals/test-platform-compat.sh',
'evals/test-feedback-auth.sh',
'evals/test-microsoft-flavor.sh',
'evals/test-heartbeat.sh',
'hooks/heartbeat.sh',
'landing/functions/api/heartbeat.ts',
'landing/migrations/0004_heartbeat.sql',
'landing/src/pages/AdminStats.tsx',
'skills/pua/references/methodology-microsoft.md',
]
for rel in required_paths:
@@ -169,6 +174,17 @@ if 'Harness Integrity (anti-cheating governance)' not in session_restore:
if 'Multi-Agent Governance Topology' not in session_restore:
errors.append('SessionStart protocol missing Multi-Agent Governance Topology injection')
if not any(any('heartbeat.sh' in hook.get('command', '') for hook in item.get('hooks', [])) for item in hooks_json.get('hooks', {}).get('SessionStart', [])):
errors.append('SessionStart missing silent heartbeat.sh registration')
heartbeat_hook = (root / 'hooks/heartbeat.sh').read_text(encoding='utf-8') if (root / 'hooks/heartbeat.sh').exists() else ''
for term in ['PUA_HEARTBEAT_ENDPOINT', '/api/heartbeat', 'offline', 'telemetry', 'feedback_frequency', '--max-time']:
if term not in heartbeat_hook:
errors.append(f'heartbeat hook missing required term: {term}')
heartbeat_api = (root / 'landing/functions/api/heartbeat.ts').read_text(encoding='utf-8') if (root / 'landing/functions/api/heartbeat.ts').exists() else ''
for term in ['ADMIN_GITHUB_LOGINS', 'getSession(request, env.SESSION_SECRET)', 'heartbeat_installs', 'heartbeat_events', 'COUNT(DISTINCT install_id_hash)', 'sha256Hex']:
if term not in heartbeat_api:
errors.append(f'heartbeat endpoint missing required term: {term}')
for forbidden in ['Applies to ALL task types', 'All task types', 'code, config, debug, deploy, research']:
if forbidden in skill.split('---', 2)[1]:
errors.append(f'pua skill description is too broad and may false-trigger: {forbidden}')
+131
View File
@@ -0,0 +1,131 @@
#!/bin/bash
# PUA silent heartbeat hook.
# Contract: best-effort, no stdout/stderr, no additionalContext, no user-visible prompt text.
exec >/dev/null 2>&1
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
# shellcheck source=/dev/null
source "${SCRIPT_DIR}/flavor-helper.sh" || exit 0
is_true() {
case "$(printf '%s' "$1" | tr '[:upper:]' '[:lower:]')" in
true|1|yes|on) return 0 ;;
*) return 1 ;;
esac
}
is_false() {
case "$(printf '%s' "$1" | tr '[:upper:]' '[:lower:]')" in
false|0|no|off) return 0 ;;
*) return 1 ;;
esac
}
CONFIG="$(pua_config_file)"
OFFLINE="False"
TELEMETRY="True"
FEEDBACK_FREQUENCY=""
FLAVOR="alibaba"
if [ -f "$CONFIG" ]; then
OFFLINE="$(pua_json_get "$CONFIG" offline False)"
TELEMETRY="$(pua_json_get "$CONFIG" telemetry True)"
FEEDBACK_FREQUENCY="$(pua_json_get "$CONFIG" feedback_frequency "")"
FLAVOR="$(pua_json_get "$CONFIG" flavor alibaba)"
fi
# Normalize Chinese/alias flavor names through the shared flavor map. This keeps
# Cloudflare stats segmented by canonical flavor ids (for example 微软 → microsoft).
get_flavor >/dev/null 2>&1 || true
FLAVOR="${PUA_FLAVOR:-$FLAVOR}"
# Privacy gates: offline/off/feedback_frequency=0 means no telemetry identity is created.
is_true "$OFFLINE" && exit 0
is_false "$TELEMETRY" && exit 0
case "$FEEDBACK_FREQUENCY" in
0|never|off|false|False) exit 0 ;;
esac
command -v curl >/dev/null 2>&1 || exit 0
STATE_DIR="${PUA_STATE_DIR:-${HOME:-.}/.pua}"
INSTALL_ID_FILE="${PUA_HEARTBEAT_ID_FILE:-${STATE_DIR}/install_id}"
LAST_FILE="${PUA_HEARTBEAT_LAST_FILE:-${STATE_DIR}/.heartbeat_last}"
mkdir -p "$STATE_DIR" || exit 0
now="$(date +%s 2>/dev/null || printf '0')"
interval="${PUA_HEARTBEAT_INTERVAL_SECONDS:-21600}"
case "$interval" in
''|*[!0-9]*) interval=21600 ;;
esac
if [ -f "$LAST_FILE" ] && [ "$interval" -gt 0 ]; then
last="$(cat "$LAST_FILE" 2>/dev/null || printf '0')"
case "$last" in ''|*[!0-9]*) last=0 ;; esac
if [ $((now - last)) -lt "$interval" ]; then
exit 0
fi
fi
new_install_id() {
if command -v uuidgen >/dev/null 2>&1; then
uuidgen | tr '[:upper:]' '[:lower:]'
elif [ -r /proc/sys/kernel/random/uuid ]; then
cat /proc/sys/kernel/random/uuid
else
printf 'pua-%s-%s-%s\n' "${now}" "$$" "$(od -An -N8 -tx1 /dev/urandom 2>/dev/null | tr -d ' \n')"
fi
}
if [ ! -s "$INSTALL_ID_FILE" ]; then
umask 077
new_install_id > "${INSTALL_ID_FILE}.tmp.$$" 2>/dev/null || exit 0
mv "${INSTALL_ID_FILE}.tmp.$$" "$INSTALL_ID_FILE" 2>/dev/null || rm -f "${INSTALL_ID_FILE}.tmp.$$"
fi
INSTALL_ID="$(cat "$INSTALL_ID_FILE" 2>/dev/null | head -n 1 | tr -cd '[:alnum:]_.:-' | cut -c1-128)"
[ -n "$INSTALL_ID" ] || exit 0
PLUGIN_ROOT="${CLAUDE_PLUGIN_ROOT:-$(cd "${SCRIPT_DIR}/.." && pwd)}"
PLUGIN_JSON="${PLUGIN_ROOT}/plugin.json"
VERSION="unknown"
PY="$(pua_python_cmd 2>/dev/null || true)"
if [ -n "$PY" ] && [ -f "$PLUGIN_JSON" ]; then
VERSION="$($PY -c 'import json,sys
try:
print(json.load(open(sys.argv[1], encoding="utf-8")).get("version", "unknown"))
except Exception:
print("unknown")' "$(pua_to_python_path "$PLUGIN_JSON")" 2>/dev/null || printf 'unknown')"
fi
PLATFORM="${PUA_HEARTBEAT_PLATFORM:-claude-code}"
EVENT_NAME="session_start"
ENDPOINT="${PUA_HEARTBEAT_ENDPOINT:-https://pua-skill.pages.dev/api/heartbeat}"
if [ -n "$PY" ]; then
PAYLOAD="$($PY -c 'import json,sys
install_id,version,platform,event_name,flavor=sys.argv[1:6]
print(json.dumps({
"install_id": install_id,
"plugin_version": version,
"platform": platform,
"event_name": event_name,
"flavor": flavor,
}, ensure_ascii=False, separators=(",", ":")))' "$INSTALL_ID" "$VERSION" "$PLATFORM" "$EVENT_NAME" "$FLAVOR" 2>/dev/null || true)"
else
PAYLOAD="{\"install_id\":\"${INSTALL_ID}\",\"plugin_version\":\"${VERSION}\",\"platform\":\"${PLATFORM}\",\"event_name\":\"${EVENT_NAME}\",\"flavor\":\"alibaba\"}"
fi
[ -n "$PAYLOAD" ] || exit 0
printf '%s\n' "$now" > "$LAST_FILE" 2>/dev/null || true
(
curl -fsS --max-time 2 \
-X POST "$ENDPOINT" \
-H "Content-Type: application/json" \
-H "User-Agent: pua-skill-heartbeat/${VERSION}" \
--data-binary "$PAYLOAD" \
>/dev/null 2>&1
) &
exit 0
+10
View File
@@ -39,6 +39,11 @@
{
"matcher": "compact",
"hooks": [
{
"type": "command",
"command": "bash \"${CLAUDE_PLUGIN_ROOT}/hooks/heartbeat.sh\"",
"timeout": 2
},
{
"type": "command",
"command": "bash \"${CLAUDE_PLUGIN_ROOT}/hooks/session-restore.sh\"",
@@ -49,6 +54,11 @@
{
"matcher": "startup|resume",
"hooks": [
{
"type": "command",
"command": "bash \"${CLAUDE_PLUGIN_ROOT}/hooks/heartbeat.sh\"",
"timeout": 2
},
{
"type": "command",
"command": "bash \"${CLAUDE_PLUGIN_ROOT}/hooks/session-restore.sh\"",
+325
View File
@@ -0,0 +1,325 @@
import { getSession } from "./_session"
interface Env {
DB: D1Database
SESSION_SECRET: string
ADMIN_GITHUB_LOGINS?: string
}
type HeartbeatBody = {
install_id?: string
plugin_version?: string
platform?: string
event_name?: string
flavor?: string
}
type CountRow = {
total_installs: number
active_24h: number
active_7d: number
active_30d: number
sessions_24h: number
sessions_7d: number
sessions_30d: number
}
const MAX_BODY_BYTES = 4 * 1024
const MAX_INSTALL_ID_BYTES = 128
const RATE_LIMIT_WINDOW_SECONDS = 60
const RATE_LIMIT_MAX_WRITES = 120
const ALLOWED_ORIGINS = new Set([
"https://openpua.ai",
"https://www.openpua.ai",
"https://pua-skill.pages.dev",
"http://localhost:5173",
"http://127.0.0.1:5173",
])
function byteLength(value: string): number {
return new TextEncoder().encode(value).byteLength
}
function corsHeadersFor(request: Request) {
const origin = request.headers.get("Origin") || ""
const allowedOrigin = origin && ALLOWED_ORIGINS.has(origin) ? origin : "*"
return {
"Access-Control-Allow-Origin": allowedOrigin,
"Access-Control-Allow-Methods": "GET, POST, OPTIONS",
"Access-Control-Allow-Headers": "Content-Type",
"Vary": "Origin",
}
}
function originAllowed(request: Request): boolean {
const origin = request.headers.get("Origin")
return !origin || ALLOWED_ORIGINS.has(origin)
}
function json(data: unknown, init: ResponseInit = {}, request?: Request): Response {
return Response.json(data, { ...init, headers: { ...(request ? corsHeadersFor(request) : {}), ...(init.headers || {}) } })
}
async function sha256Hex(value: string): Promise<string> {
const digest = await crypto.subtle.digest("SHA-256", new TextEncoder().encode(value))
return [...new Uint8Array(digest)].map((b) => b.toString(16).padStart(2, "0")).join("")
}
function cleanText(value: unknown, maxBytes: number, fallback = ""): string {
if (value === undefined || value === null) return fallback
const text = String(value).trim()
if (!text) return fallback
if (byteLength(text) > maxBytes) throw new Error(`field too large; max ${maxBytes} bytes`)
return text
}
function cleanEnumText(value: unknown, maxBytes: number, fallback: string): string {
const text = cleanText(value, maxBytes, fallback)
const normalized = text.replace(/[^a-zA-Z0-9_.:/@ -]/g, "").trim().slice(0, maxBytes)
return normalized || fallback
}
function validateHeartbeatBody(body: HeartbeatBody) {
const installId = cleanText(body.install_id, MAX_INSTALL_ID_BYTES)
if (!installId) throw new Error("install_id is required")
return {
install_id: installId,
plugin_version: cleanEnumText(body.plugin_version, 48, "unknown"),
platform: cleanEnumText(body.platform, 64, "claude-code"),
event_name: cleanEnumText(body.event_name, 64, "session_start") || "session_start",
flavor: cleanEnumText(body.flavor, 64, "unknown"),
}
}
async function rateLimitKey(request: Request): Promise<string> {
const ip = request.headers.get("CF-Connecting-IP") || request.headers.get("X-Forwarded-For") || "unknown"
const ua = request.headers.get("User-Agent") || "unknown"
return sha256Hex(`${ip}|${ua}|heartbeat-v1`)
}
async function ensureHeartbeatTables(env: Env): Promise<void> {
await env.DB.prepare(`
CREATE TABLE IF NOT EXISTS heartbeat_installs (
install_id_hash TEXT PRIMARY KEY,
first_seen DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
last_seen DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
session_count INTEGER NOT NULL DEFAULT 0,
last_plugin_version TEXT,
last_platform TEXT,
last_flavor TEXT,
last_ip_country TEXT,
last_user_agent_hash TEXT
)
`).run()
await env.DB.prepare(`
CREATE TABLE IF NOT EXISTS heartbeat_events (
id INTEGER PRIMARY KEY AUTOINCREMENT,
install_id_hash TEXT NOT NULL,
event_name TEXT NOT NULL DEFAULT 'session_start',
plugin_version TEXT,
platform TEXT,
flavor TEXT,
ip_country TEXT,
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP
)
`).run()
await env.DB.prepare(`
CREATE TABLE IF NOT EXISTS heartbeat_rate_limits (
key TEXT NOT NULL,
window_start INTEGER NOT NULL,
count INTEGER NOT NULL DEFAULT 0,
updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (key, window_start)
)
`).run()
}
async function checkRateLimit(request: Request, env: Env): Promise<boolean> {
await ensureHeartbeatTables(env)
const key = await rateLimitKey(request)
const windowStart = Math.floor(Date.now() / (RATE_LIMIT_WINDOW_SECONDS * 1000)) * RATE_LIMIT_WINDOW_SECONDS
await env.DB.prepare(`
INSERT INTO heartbeat_rate_limits (key, window_start, count, updated_at)
VALUES (?, ?, 1, datetime('now'))
ON CONFLICT(key, window_start)
DO UPDATE SET count = count + 1, updated_at = datetime('now')
`).bind(key, windowStart).run()
const row = await env.DB.prepare(
"SELECT count FROM heartbeat_rate_limits WHERE key = ? AND window_start = ?"
).bind(key, windowStart).first<{ count: number }>()
await env.DB.prepare(
"DELETE FROM heartbeat_rate_limits WHERE window_start < ?"
).bind(windowStart - RATE_LIMIT_WINDOW_SECONDS * 60).run()
return (row?.count || 0) <= RATE_LIMIT_MAX_WRITES
}
function adminLogins(env: Env): Set<string> {
const configured = (env.ADMIN_GITHUB_LOGINS || "tanweai,xsser")
.split(",")
.map((item) => item.trim().toLowerCase())
.filter(Boolean)
return new Set(configured)
}
async function handlePost(request: Request, env: Env): Promise<Response> {
if (!originAllowed(request)) {
return json({ error: "Origin not allowed" }, { status: 403 }, request)
}
const contentLength = Number(request.headers.get("content-length") || 0)
if (contentLength > MAX_BODY_BYTES) {
return json({ error: `Request body too large (max ${MAX_BODY_BYTES} bytes)` }, { status: 413 }, request)
}
let bodyText = ""
try { bodyText = await request.text() } catch {}
if (!bodyText) return json({ error: "JSON body required" }, { status: 400 }, request)
if (byteLength(bodyText) > MAX_BODY_BYTES) {
return json({ error: `Request body too large (max ${MAX_BODY_BYTES} bytes)` }, { status: 413 }, request)
}
try {
const body = validateHeartbeatBody(JSON.parse(bodyText) as HeartbeatBody)
const allowed = await checkRateLimit(request, env)
if (!allowed) return json({ error: "Rate limit exceeded" }, { status: 429 }, request)
const installIdHash = await sha256Hex(`pua-install:${body.install_id}`)
const userAgent = request.headers.get("User-Agent") || "unknown"
const userAgentHash = await sha256Hex(`ua:${userAgent}`)
const ipCountry = request.headers.get("CF-IPCountry") || "unknown"
await env.DB.prepare(`
INSERT INTO heartbeat_installs (
install_id_hash, first_seen, last_seen, session_count,
last_plugin_version, last_platform, last_flavor, last_ip_country, last_user_agent_hash
) VALUES (?, datetime('now'), datetime('now'), 1, ?, ?, ?, ?, ?)
ON CONFLICT(install_id_hash)
DO UPDATE SET
last_seen = datetime('now'),
session_count = session_count + 1,
last_plugin_version = excluded.last_plugin_version,
last_platform = excluded.last_platform,
last_flavor = excluded.last_flavor,
last_ip_country = excluded.last_ip_country,
last_user_agent_hash = excluded.last_user_agent_hash
`).bind(
installIdHash,
body.plugin_version,
body.platform,
body.flavor,
ipCountry,
userAgentHash
).run()
await env.DB.prepare(`
INSERT INTO heartbeat_events (install_id_hash, event_name, plugin_version, platform, flavor, ip_country, created_at)
VALUES (?, ?, ?, ?, ?, ?, datetime('now'))
`).bind(
installIdHash,
body.event_name,
body.plugin_version,
body.platform,
body.flavor,
ipCountry
).run()
return json({ ok: true }, {}, request)
} catch (e) {
const message = String((e as Error).message || e)
const status = /too large/.test(message) ? 413 : (/required|Unexpected token|JSON/.test(message) ? 400 : 500)
return json({ error: status === 500 ? "Failed to save heartbeat" : message }, { status }, request)
}
}
async function handleGet(request: Request, env: Env): Promise<Response> {
if (!env.SESSION_SECRET) {
return json({ error: "Admin session is not configured" }, { status: 500 }, request)
}
const session = await getSession(request, env.SESSION_SECRET)
if (!session) return json({ error: "Login required" }, { status: 401 }, request)
if (!adminLogins(env).has(session.login.toLowerCase())) {
return json({ error: "Admin access required" }, { status: 403 }, request)
}
await ensureHeartbeatTables(env)
const totals = await env.DB.prepare(`
SELECT
(SELECT COUNT(*) FROM heartbeat_installs) AS total_installs,
(SELECT COUNT(*) FROM heartbeat_installs WHERE last_seen >= datetime('now', '-1 day')) AS active_24h,
(SELECT COUNT(*) FROM heartbeat_installs WHERE last_seen >= datetime('now', '-7 days')) AS active_7d,
(SELECT COUNT(*) FROM heartbeat_installs WHERE last_seen >= datetime('now', '-30 days')) AS active_30d,
(SELECT COUNT(*) FROM heartbeat_events WHERE created_at >= datetime('now', '-1 day')) AS sessions_24h,
(SELECT COUNT(*) FROM heartbeat_events WHERE created_at >= datetime('now', '-7 days')) AS sessions_7d,
(SELECT COUNT(*) FROM heartbeat_events WHERE created_at >= datetime('now', '-30 days')) AS sessions_30d
`).first<CountRow>()
const daily = await env.DB.prepare(`
SELECT date(created_at) AS day, COUNT(DISTINCT install_id_hash) AS active_users, COUNT(*) AS sessions
FROM heartbeat_events
WHERE created_at >= datetime('now', '-30 days')
GROUP BY day
ORDER BY day DESC
`).all()
const byVersion = await env.DB.prepare(`
SELECT COALESCE(plugin_version, 'unknown') AS plugin_version,
COUNT(DISTINCT install_id_hash) AS active_users,
COUNT(*) AS sessions
FROM heartbeat_events
WHERE created_at >= datetime('now', '-30 days')
GROUP BY plugin_version
ORDER BY active_users DESC, sessions DESC
LIMIT 20
`).all()
const byPlatform = await env.DB.prepare(`
SELECT COALESCE(platform, 'unknown') AS platform,
COUNT(DISTINCT install_id_hash) AS active_users,
COUNT(*) AS sessions
FROM heartbeat_events
WHERE created_at >= datetime('now', '-30 days')
GROUP BY platform
ORDER BY active_users DESC, sessions DESC
LIMIT 20
`).all()
const byFlavor = await env.DB.prepare(`
SELECT COALESCE(flavor, 'unknown') AS flavor,
COUNT(DISTINCT install_id_hash) AS active_users,
COUNT(*) AS sessions
FROM heartbeat_events
WHERE created_at >= datetime('now', '-30 days')
GROUP BY flavor
ORDER BY active_users DESC, sessions DESC
LIMIT 20
`).all()
return json({
ok: true,
generated_at: new Date().toISOString(),
viewer: { login: session.login, avatar: session.avatar },
totals: totals || {
total_installs: 0,
active_24h: 0,
active_7d: 0,
active_30d: 0,
sessions_24h: 0,
sessions_7d: 0,
sessions_30d: 0,
},
daily: daily.results,
by_version: byVersion.results,
by_platform: byPlatform.results,
by_flavor: byFlavor.results,
}, {}, request)
}
export const onRequest: PagesFunction<Env> = async ({ request, env }) => {
if (request.method === "OPTIONS") {
return new Response(null, { status: 204, headers: corsHeadersFor(request) })
}
if (request.method === "POST") return handlePost(request, env)
if (request.method === "GET") return handleGet(request, env)
return json({ error: "Method not allowed" }, { status: 405 }, request)
}
+37
View File
@@ -0,0 +1,37 @@
-- Silent heartbeat telemetry: anonymous install aggregates + admin stats.
CREATE TABLE IF NOT EXISTS heartbeat_installs (
install_id_hash TEXT PRIMARY KEY,
first_seen DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
last_seen DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
session_count INTEGER NOT NULL DEFAULT 0,
last_plugin_version TEXT,
last_platform TEXT,
last_flavor TEXT,
last_ip_country TEXT,
last_user_agent_hash TEXT
);
CREATE TABLE IF NOT EXISTS heartbeat_events (
id INTEGER PRIMARY KEY AUTOINCREMENT,
install_id_hash TEXT NOT NULL,
event_name TEXT NOT NULL DEFAULT 'session_start',
plugin_version TEXT,
platform TEXT,
flavor TEXT,
ip_country TEXT,
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP
);
CREATE TABLE IF NOT EXISTS heartbeat_rate_limits (
key TEXT NOT NULL,
window_start INTEGER NOT NULL,
count INTEGER NOT NULL DEFAULT 0,
updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (key, window_start)
);
CREATE INDEX IF NOT EXISTS idx_heartbeat_installs_last_seen ON heartbeat_installs(last_seen);
CREATE INDEX IF NOT EXISTS idx_heartbeat_events_created_at ON heartbeat_events(created_at);
CREATE INDEX IF NOT EXISTS idx_heartbeat_events_version ON heartbeat_events(plugin_version, created_at);
CREATE INDEX IF NOT EXISTS idx_heartbeat_events_platform ON heartbeat_events(platform, created_at);
CREATE INDEX IF NOT EXISTS idx_heartbeat_events_flavor ON heartbeat_events(flavor, created_at);
+4
View File
@@ -16,6 +16,7 @@ import {
NumberTicker,
} from "./components/motion-primitives"
import Contribute from "./pages/Contribute"
import AdminStats from "./pages/AdminStats"
import {
type Lang,
LANG_LABELS,
@@ -323,6 +324,9 @@ export default function App() {
if (hash === "#/contribute") {
return <Contribute lang={lang} />
}
if (hash === "#/admin/heartbeats") {
return <AdminStats lang={lang} />
}
const L = (o: Record<Lang, string>) => o[lang]
const activeBenchmark = BENCHMARKS.find((benchmark) => benchmark.name === activeTab) ?? BENCHMARKS[0]
+213
View File
@@ -0,0 +1,213 @@
import { useEffect, useState } from "react"
import type { Lang } from "../i18n"
type Totals = {
total_installs: number
active_24h: number
active_7d: number
active_30d: number
sessions_24h: number
sessions_7d: number
sessions_30d: number
}
type DailyRow = { day: string; active_users: number; sessions: number }
type BreakdownRow = { active_users: number; sessions: number; [key: string]: string | number }
type StatsData = {
ok: true
generated_at: string
viewer: { login: string; avatar: string }
totals: Totals
daily: DailyRow[]
by_version: BreakdownRow[]
by_platform: BreakdownRow[]
by_flavor: BreakdownRow[]
}
type LoadState =
| { status: "loading" }
| { status: "ready"; data: StatsData }
| { status: "unauthorized" }
| { status: "forbidden" }
| { status: "error"; message: string }
interface Props {
lang: Lang
}
function cardStyle(): React.CSSProperties {
return {
border: "1px solid var(--border)",
borderRadius: "1rem",
background: "var(--bg)",
padding: "1.25rem",
boxShadow: "0 12px 40px rgba(15, 23, 42, 0.06)",
}
}
function StatCard({ label, value, hint }: { label: string; value: number; hint?: string }) {
return (
<div style={cardStyle()}>
<div style={{ color: "var(--text-muted)", fontSize: "0.78rem", fontWeight: 700, textTransform: "uppercase", letterSpacing: "0.08em" }}>{label}</div>
<div style={{ marginTop: "0.45rem", fontSize: "2rem", fontWeight: 800, letterSpacing: "-0.04em" }}>{value.toLocaleString()}</div>
{hint && <div style={{ marginTop: "0.35rem", color: "var(--text-muted)", fontSize: "0.82rem" }}>{hint}</div>}
</div>
)
}
function BreakdownTable({ title, rows, labelKey }: { title: string; rows: BreakdownRow[]; labelKey: string }) {
return (
<section style={cardStyle()}>
<h2 style={{ margin: "0 0 1rem", fontSize: "1rem", letterSpacing: "-0.02em" }}>{title}</h2>
<div style={{ overflowX: "auto" }}>
<table style={{ width: "100%", borderCollapse: "collapse", fontSize: "0.86rem" }}>
<thead>
<tr style={{ color: "var(--text-muted)", textAlign: "left" }}>
<th style={{ padding: "0.5rem 0", borderBottom: "1px solid var(--border)" }}>Segment</th>
<th style={{ padding: "0.5rem 0", borderBottom: "1px solid var(--border)", textAlign: "right" }}>Active users</th>
<th style={{ padding: "0.5rem 0", borderBottom: "1px solid var(--border)", textAlign: "right" }}>Sessions</th>
</tr>
</thead>
<tbody>
{rows.map((row) => (
<tr key={String(row[labelKey])}>
<td style={{ padding: "0.6rem 0", borderBottom: "1px solid var(--border)", fontWeight: 600 }}>{String(row[labelKey] || "unknown")}</td>
<td style={{ padding: "0.6rem 0", borderBottom: "1px solid var(--border)", textAlign: "right" }}>{Number(row.active_users || 0).toLocaleString()}</td>
<td style={{ padding: "0.6rem 0", borderBottom: "1px solid var(--border)", textAlign: "right" }}>{Number(row.sessions || 0).toLocaleString()}</td>
</tr>
))}
{rows.length === 0 && (
<tr><td colSpan={3} style={{ padding: "1rem 0", color: "var(--text-muted)" }}>No data yet</td></tr>
)}
</tbody>
</table>
</div>
</section>
)
}
export default function AdminStats({ lang }: Props) {
const L = (zh: string, en: string) => (lang === "zh" ? zh : en)
const [state, setState] = useState<LoadState>({ status: "loading" })
useEffect(() => {
let cancelled = false
fetch("/api/heartbeat", { credentials: "include" })
.then(async (response) => {
if (response.status === 401) return { kind: "unauthorized" as const }
if (response.status === 403) return { kind: "forbidden" as const }
if (!response.ok) throw new Error(`HTTP ${response.status}`)
return { kind: "ready" as const, data: await response.json() as StatsData }
})
.then((result) => {
if (cancelled) return
if (result.kind === "ready") setState({ status: "ready", data: result.data })
if (result.kind === "unauthorized") setState({ status: "unauthorized" })
if (result.kind === "forbidden") setState({ status: "forbidden" })
})
.catch((error: Error) => {
if (!cancelled) setState({ status: "error", message: error.message })
})
return () => { cancelled = true }
}, [])
const header = (
<div style={{ background: "var(--bg)", borderBottom: "1px solid var(--border)", padding: "1.5rem" }}>
<div className="container" style={{ display: "flex", alignItems: "center", justifyContent: "space-between", gap: "1rem" }}>
<a href="/" style={{ textDecoration: "none", color: "inherit", display: "flex", alignItems: "center", gap: "0.75rem" }}>
<span style={{ fontWeight: 800, fontSize: "1.35rem", letterSpacing: "-0.04em" }}>pua</span>
<span style={{ color: "var(--text-muted)" }}>/</span>
<span style={{ fontSize: "0.95rem", fontWeight: 600 }}>{L("活跃统计", "Heartbeat Stats")}</span>
</a>
{state.status === "ready" && (
<div style={{ display: "flex", alignItems: "center", gap: "0.65rem", color: "var(--text-muted)", fontSize: "0.85rem" }}>
<img src={state.data.viewer.avatar} alt={state.data.viewer.login} style={{ width: "1.8rem", height: "1.8rem", borderRadius: "999px" }} />
<span>{state.data.viewer.login}</span>
</div>
)}
</div>
</div>
)
if (state.status === "loading") {
return <div style={{ minHeight: "100vh", background: "var(--bg-alt)" }}>{header}<div className="container" style={{ padding: "3rem 1.5rem", color: "var(--text-muted)" }}>Loading...</div></div>
}
if (state.status === "unauthorized" || state.status === "forbidden" || state.status === "error") {
const message = state.status === "unauthorized"
? L("需要先用 GitHub 登录。", "GitHub login is required.")
: state.status === "forbidden"
? L("当前 GitHub 账号不在管理员白名单。", "This GitHub account is not on the admin allowlist.")
: state.message
return (
<div style={{ minHeight: "100vh", background: "var(--bg-alt)" }}>
{header}
<main className="container" style={{ padding: "3rem 1.5rem" }}>
<section style={{ ...cardStyle(), maxWidth: "34rem", margin: "0 auto", textAlign: "center" }}>
<h1 style={{ margin: "0 0 0.75rem", fontSize: "1.5rem", letterSpacing: "-0.03em" }}>{L("管理员统计", "Admin Stats")}</h1>
<p style={{ color: "var(--text-secondary)", lineHeight: 1.7 }}>{message}</p>
{state.status === "unauthorized" && <a href="/api/auth/github" className="btn-primary" style={{ marginTop: "1rem", display: "inline-flex" }}>{L("使用 GitHub 登录", "Login with GitHub")}</a>}
</section>
</main>
</div>
)
}
const { totals } = state.data
return (
<div style={{ minHeight: "100vh", background: "var(--bg-alt)" }}>
{header}
<main className="container" style={{ padding: "3rem 1.5rem" }}>
<div style={{ marginBottom: "2rem" }}>
<h1 style={{ margin: 0, fontSize: "2.2rem", letterSpacing: "-0.05em" }}>{L("PUA Skill 活跃用户", "PUA Skill Active Users")}</h1>
<p style={{ color: "var(--text-muted)", lineHeight: 1.7, maxWidth: "48rem" }}>
{L("只展示匿名聚合数据:安装标识只以 SHA-256 哈希保存,页面需要管理员 GitHub 登录。", "Anonymous aggregate data only: install identifiers are stored as SHA-256 hashes and this page requires admin GitHub login.")}
</p>
<p style={{ color: "var(--text-muted)", fontSize: "0.82rem" }}>Generated at {state.data.generated_at}</p>
</div>
<section style={{ display: "grid", gridTemplateColumns: "repeat(auto-fit, minmax(12rem, 1fr))", gap: "1rem", marginBottom: "1.25rem" }}>
<StatCard label="Total installs" value={totals.total_installs} />
<StatCard label="Active 24h" value={totals.active_24h} hint={`${totals.sessions_24h.toLocaleString()} sessions`} />
<StatCard label="Active 7d" value={totals.active_7d} hint={`${totals.sessions_7d.toLocaleString()} sessions`} />
<StatCard label="Active 30d" value={totals.active_30d} hint={`${totals.sessions_30d.toLocaleString()} sessions`} />
</section>
<section style={{ ...cardStyle(), marginBottom: "1.25rem" }}>
<h2 style={{ margin: "0 0 1rem", fontSize: "1rem", letterSpacing: "-0.02em" }}>{L("最近 30 天", "Last 30 days")}</h2>
<div style={{ overflowX: "auto" }}>
<table style={{ width: "100%", borderCollapse: "collapse", fontSize: "0.86rem" }}>
<thead>
<tr style={{ color: "var(--text-muted)", textAlign: "left" }}>
<th style={{ padding: "0.5rem 0", borderBottom: "1px solid var(--border)" }}>Day</th>
<th style={{ padding: "0.5rem 0", borderBottom: "1px solid var(--border)", textAlign: "right" }}>Active users</th>
<th style={{ padding: "0.5rem 0", borderBottom: "1px solid var(--border)", textAlign: "right" }}>Sessions</th>
</tr>
</thead>
<tbody>
{state.data.daily.map((row) => (
<tr key={row.day}>
<td style={{ padding: "0.6rem 0", borderBottom: "1px solid var(--border)", fontWeight: 600 }}>{row.day}</td>
<td style={{ padding: "0.6rem 0", borderBottom: "1px solid var(--border)", textAlign: "right" }}>{row.active_users.toLocaleString()}</td>
<td style={{ padding: "0.6rem 0", borderBottom: "1px solid var(--border)", textAlign: "right" }}>{row.sessions.toLocaleString()}</td>
</tr>
))}
{state.data.daily.length === 0 && (
<tr><td colSpan={3} style={{ padding: "1rem 0", color: "var(--text-muted)" }}>No data yet</td></tr>
)}
</tbody>
</table>
</div>
</section>
<div style={{ display: "grid", gridTemplateColumns: "repeat(auto-fit, minmax(18rem, 1fr))", gap: "1rem" }}>
<BreakdownTable title="By version" rows={state.data.by_version} labelKey="plugin_version" />
<BreakdownTable title="By platform" rows={state.data.by_platform} labelKey="platform" />
<BreakdownTable title="By flavor" rows={state.data.by_flavor} labelKey="flavor" />
</div>
</main>
</div>
)
}
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@tanweai/pi-pua",
"version": "3.4.2",
"version": "3.4.3",
"description": "PUA high-agency governance extension and skill pack for the Pi coding agent.",
"type": "module",
"license": "MIT",
+6 -3
View File
@@ -1,7 +1,7 @@
{
"name": "pua",
"version": "3.4.2",
"description": "Forces high-agency exhaustive problem-solving with corporate PUA pressure escalation when the AI is passive, gives up, skips verification, produces low-quality work, or the user explicitly asks for PUA/try-harder mode. v3.4.2 restores the authentic Microsoft flavor with Connects, Impact Descriptors, SLITE/LITE, PIP/GVSA, and Three Circles of Impact for fixed-thinking failures. Does not target normal first-attempt requests.",
"version": "3.4.3",
"description": "Forces high-agency exhaustive problem-solving with corporate PUA pressure escalation when the AI is passive, gives up, skips verification, produces low-quality work, or the user explicitly asks for PUA/try-harder mode. v3.4.3 adds a silent heartbeat hook with admin-only Cloudflare active-user stats while keeping heartbeat details out of conversation prompts. Does not target normal first-attempt requests.",
"author": {
"name": "探微安全实验室",
"url": "https://github.com/tanweai"
@@ -33,6 +33,9 @@
"trae",
"codex-aliases",
"diagnosis-first",
"rate-limit"
"rate-limit",
"heartbeat",
"cloudflare",
"active-users"
]
}