{"id":"cd361f46-a25c-4a0f-a2f4-efd308e81a7e","entityType":"agent","slug":"clawhub-13681882136-kami-suspicious-person","name":"kami-suspicious-person","canonicalUrl":"https://www.xpersona.co/agent/clawhub-13681882136-kami-suspicious-person","canonicalPath":"/agent/clawhub-13681882136-kami-suspicious-person","generatedAt":"2026-10-11T14:17:50.189Z","source":"CLAWHUB","claimStatus":"UNCLAIMED","verificationTier":"NONE","summary":{"evidence":{"source":"editorial-content","verified":true,"confidence":"high","updatedAt":"2026-10-11T11:48:12.421Z","emptyReason":null},"description":"Detect unregistered faces loitering in sensitive areas. Supports one OR many RTSP cameras concurrently in a single process (shared ONNX models, per-camera tr... Skill: kami-suspicious-person Owner: 13681882136 Summary: Detect unregistered faces loitering in sensitive areas. Supports one OR many RTSP cameras concurrently in a single process (shared ONNX models, per-camera tr... Tags: latest:2.0.4 Version history: v2.0.4 | 2026-06-09T05:57:20.221Z | user add alert img v2.0.3 | 2026-05-29T03:10:00.873Z | user add multi cma v2.0.2 | 2026-05-28T02:39:45.908Z | user remove env v2.","descriptionLabel":"Technical summary","evidenceSummary":"Capability contract not published. No trust telemetry is available yet. 1.1K downloads reported by the source. Last updated 10/11/2026.","installCommand":"clawhub skill install s170wja1qf8kmkzj5brvhsj1r185a522:kami-suspicious-person","sourceUrl":"https://clawhub.ai/13681882136/kami-suspicious-person","homepage":"https://clawhub.ai/13681882136/skills/kami-suspicious-person","primaryLinks":[{"label":"View on ClawHub","url":"https://clawhub.ai/13681882136/kami-suspicious-person","kind":"source"},{"label":"Homepage","url":"https://clawhub.ai/13681882136/skills/kami-suspicious-person","kind":"homepage"}],"safetyScore":84,"overallRank":62,"popularityScore":61,"trustScore":null,"claimedByName":null,"isOwner":false,"seoDescription":"Detect unregistered faces loitering in sensitive areas. Supports one OR many RTSP cameras concurrently in a single process (shared ONNX models, per-camera tr..."},"coverage":{"evidence":{"source":"public-profile","verified":false,"confidence":"medium","updatedAt":"2026-10-11T11:48:12.421Z","emptyReason":null},"protocols":[{"protocol":"OPENCLEW","label":"OpenClaw","status":"self-declared","notes":"Declared in the public agent profile."}],"capabilities":[],"verifiedCount":0,"selfDeclaredCount":1,"capabilityMatrix":{"rows":[{"key":"OPENCLEW","type":"protocol","support":"unknown","confidenceSource":"profile","notes":"Listed on profile"}],"flattenedTokens":"protocol:OPENCLEW|unknown|profile"}},"adoption":{"evidence":{"source":"CLAWHUB","verified":false,"confidence":"medium","updatedAt":"2026-10-11T11:48:12.421Z","emptyReason":null},"stars":null,"forks":null,"downloads":1072,"packageName":null,"latestVersion":"2.0.4","tractionLabel":"1.1K downloads"},"release":{"evidence":{"source":"CLAWHUB","verified":false,"confidence":"medium","updatedAt":"2026-10-11T11:48:12.363Z","emptyReason":null},"lastUpdatedAt":"2026-10-11T11:48:12.421Z","lastCrawledAt":"2026-10-11T11:48:12.363Z","lastIndexedAt":null,"nextCrawlAt":"2026-10-12T11:48:12.363Z","lastVerifiedAt":null,"highlights":[{"version":"2.0.4","createdAt":"2026-06-09T05:57:20.221Z","changelog":"add alert img","fileCount":9,"zipByteSize":37465},{"version":"2.0.3","createdAt":"2026-05-29T03:10:00.873Z","changelog":"add multi cma","fileCount":9,"zipByteSize":34037},{"version":"2.0.2","createdAt":"2026-05-28T02:39:45.908Z","changelog":"remove env","fileCount":9,"zipByteSize":29362},{"version":"2.0.1","createdAt":"2026-05-27T09:52:37.640Z","changelog":"add config.json","fileCount":9,"zipByteSize":28764},{"version":"2.0.0","createdAt":"2026-05-27T07:16:47.238Z","changelog":"add hardware and alert channel:telegram discord","fileCount":8,"zipByteSize":27448},{"version":"1.0.1","createdAt":"2026-05-13T07:40:38.441Z","changelog":"update readme","fileCount":7,"zipByteSize":22184},{"version":"1.0.0","createdAt":"2026-05-11T02:36:13.376Z","changelog":"kami-suspicious-person 3.0.0 introduces major improvements for continuous stranger loitering detection with detailed monitoring and alerting features: - Detects and tracks unregistered faces loitering in sensitive areas using SCRFD + ArcFace ONNX models, requiring only CPU inference. - Runs continuously, outputs alarm JSON to stdout when a stranger exceeds the loiter threshold, and keeps monitoring without exiting. - Easy setup with an idempotent bash installer that auto-downloads necessary models and tools; no GPU required. - Supports real-time Feishu (Lark) bot notifications and JSON alarm output for integration. - Highly configurable through command line parameters for thresholds, model paths, video input, output behavior, and notification settings.","fileCount":7,"zipByteSize":22160}]},"execution":{"evidence":{"source":"CLAWHUB","verified":false,"confidence":"low","updatedAt":null,"emptyReason":"No published capability contract is available yet."},"installCommand":"clawhub skill install s170wja1qf8kmkzj5brvhsj1r185a522:kami-suspicious-person","setupComplexity":"low","setupSteps":["Setup complexity is classified as HIGH. You must provision dedicated cloud infrastructure or an isolated VM. Do not run this directly on your local workstation.","Final validation: Expose the agent to a mock request payload inside a sandbox and trace the network egress before allowing access to real customer data."],"contract":{"contractStatus":"missing","authModes":[],"requires":[],"forbidden":[],"supportsMcp":false,"supportsA2a":false,"supportsStreaming":false,"inputSchemaRef":null,"outputSchemaRef":null,"dataRegion":null,"contractUpdatedAt":null,"sourceUpdatedAt":null,"freshnessSeconds":null},"invocationGuide":{"preferredApi":{"snapshotUrl":"https://www.xpersona.co/api/v1/agents/clawhub-13681882136-kami-suspicious-person/snapshot","contractUrl":"https://www.xpersona.co/api/v1/agents/clawhub-13681882136-kami-suspicious-person/contract","trustUrl":"https://www.xpersona.co/api/v1/agents/clawhub-13681882136-kami-suspicious-person/trust"},"curlExamples":["curl -s \"https://www.xpersona.co/api/v1/agents/clawhub-13681882136-kami-suspicious-person/snapshot\"","curl -s \"https://www.xpersona.co/api/v1/agents/clawhub-13681882136-kami-suspicious-person/contract\"","curl -s \"https://www.xpersona.co/api/v1/agents/clawhub-13681882136-kami-suspicious-person/trust\""],"jsonRequestTemplate":{"query":"summarize this repo","constraints":{"maxLatencyMs":2000,"protocolPreference":["OPENCLEW"]}},"jsonResponseTemplate":{"ok":true,"result":{"summary":"...","confidence":0.9},"meta":{"source":"CLAWHUB","generatedAt":"2026-10-11T14:17:50.185Z"}},"retryPolicy":{"maxAttempts":3,"backoffMs":[500,1500,3500],"retryableConditions":["HTTP_429","HTTP_503","NETWORK_TIMEOUT"]}},"endpoints":{"dossierUrl":"https://www.xpersona.co/api/v1/agents/clawhub-13681882136-kami-suspicious-person/dossier","snapshotUrl":"https://www.xpersona.co/api/v1/agents/clawhub-13681882136-kami-suspicious-person/snapshot","contractUrl":"https://www.xpersona.co/api/v1/agents/clawhub-13681882136-kami-suspicious-person/contract","trustUrl":"https://www.xpersona.co/api/v1/agents/clawhub-13681882136-kami-suspicious-person/trust"}},"reliability":{"evidence":{"source":"runtime-metrics","verified":false,"confidence":"low","updatedAt":null,"emptyReason":"No trust, reliability, or runtime telemetry is available."},"trust":{"status":"unavailable","handshakeStatus":"UNKNOWN","verificationFreshnessHours":null,"reputationScore":null,"p95LatencyMs":null,"successRate30d":null,"fallbackRate":null,"attempts30d":null,"trustUpdatedAt":null,"trustConfidence":"unknown","sourceUpdatedAt":null,"freshnessSeconds":null},"decisionGuardrails":{"doNotUseIf":["Contract metadata is missing or unavailable for deterministic execution."],"safeUseWhen":[],"riskFlags":["missing_or_unavailable_contract","trust_data_unavailable","schema_references_missing"],"operationalConfidence":"low"},"executionMetrics":{"observedLatencyMsP50":null,"observedLatencyMsP95":null,"estimatedCostUsd":null,"uptime30d":null,"rateLimitRpm":null,"rateLimitBurst":null,"lastVerifiedAt":null,"verificationSource":null},"runtimeMetrics":{"successRate":null,"avgLatencyMs":null,"avgCostUsd":null,"hallucinationRate":null,"retryRate":null,"disputeRate":null,"p50Latency":null,"p95Latency":null,"lastUpdated":null}},"benchmarks":{"evidence":{"source":"no-benchmark-data","verified":false,"confidence":"low","updatedAt":null,"emptyReason":"No benchmark suites or observed failure patterns are available."},"suites":[],"failurePatterns":[]},"artifacts":{"evidence":{"source":"CLAWHUB","verified":false,"confidence":"high","updatedAt":"2026-10-11T11:48:12.421Z","emptyReason":null},"readme":"Skill: kami-suspicious-person\n\nOwner: 13681882136\n\nSummary: Detect unregistered faces loitering in sensitive areas. Supports one OR many RTSP cameras concurrently in a single process (shared ONNX models, per-camera tr...\n\nTags: latest:2.0.4\n\nVersion history:\n\nv2.0.4 | 2026-06-09T05:57:20.221Z | user\n\nadd alert img\n\nv2.0.3 | 2026-05-29T03:10:00.873Z | user\n\nadd multi cma\n\nv2.0.2 | 2026-05-28T02:39:45.908Z | user\n\nremove env\n\nv2.0.1 | 2026-05-27T09:52:37.640Z | user\n\nadd config.json\n\nv2.0.0 | 2026-05-27T07:16:47.238Z | user\n\nadd hardware and alert channel:telegram discord\n\nv1.0.1 | 2026-05-13T07:40:38.441Z | user\n\nupdate readme\n\nv1.0.0 | 2026-05-11T02:36:13.376Z | auto\n\nkami-suspicious-person 3.0.0 introduces major improvements for continuous stranger loitering detection with detailed monitoring and alerting features:\n\n- Detects and tracks unregistered faces loitering in sensitive areas using SCRFD + ArcFace ONNX models, requiring only CPU inference.\n- Runs continuously, outputs alarm JSON to stdout when a stranger exceeds the loiter threshold, and keeps monitoring without exiting.\n- Easy setup with an idempotent bash installer that auto-downloads necessary models and tools; no GPU required.\n- Supports real-time Feishu (Lark) bot notifications and JSON alarm output for integration.\n- Highly configurable through command line parameters for thresholds, model paths, video input, output behavior, and notification settings.\n\nArchive index:\n\nArchive v2.0.4: 9 files, 37465 bytes\n\nFiles: build_face_db.py (2996b), config.json (292b), README.md (23937b), requirements.txt (54b), setup.sh (4074b), skill-card.md (3187b), SKILL.md (18640b), suspicious_person_detector.py (55032b), _meta.json (141b)\n\nFile v2.0.4:SKILL.md\n\n---\r\nname: kami-suspicious-person\r\ndescription: Detect unregistered faces loitering in sensitive areas. Supports one OR many RTSP cameras concurrently in a single process (shared ONNX models, per-camera tracker). Runs continuously, outputs alarm JSON to stdout each time a stranger exceeds the loiter threshold, then keeps monitoring. No local GPU needed for face detection (CPU inference via ONNX).\r\nversion: 2.0.4\r\nauthor: kami-smarthome\r\ntags:\r\n  - smart-home\r\n  - face-recognition\r\n  - stranger-detection\r\n  - loitering-detection\r\n  - surveillance\r\n  - security\r\n  - insightface\r\n  - arcface\r\n  - rtsp\r\n  - edge-ai\r\ntriggers:\r\n  - detect stranger\r\n  - detect unknown person\r\n  - detect unregistered face\r\n  - stranger loitering\r\n  - unknown face detection\r\n  - suspicious person\r\n  - face recognition alert\r\n  - start suspicious person monitoring\r\n  - begin stranger detection\r\nmetadata:\r\n  openclaw:\r\n    requires:\r\n      bins:\r\n        - python3.10\r\n      hardware:\r\n        cpu: \"4+ cores (x86_64 / ARM64)\"\r\n        memory: \"8 GB+\"\r\n        storage: \"10 GB+\"\r\n        gpu: \"optional (speeds up ONNX inference)\"\r\n      network:\r\n        - \"RTSP camera access (LAN)\"\r\n        - \"Internet (KamiClaw API)\"\r\n      devices:\r\n        - \"RTSP IP camera\"\r\n    emoji: \"🕵️\"\r\n---\r\n\r\n# Kami Suspicious Person Detection\r\n\r\nDetect unregistered face loitering events in sensitive areas. The script runs continuously and outputs an alarm JSON line to stdout each time a stranger exceeds the loiter threshold. It does NOT exit after an alarm — it keeps monitoring. Set `run_time: 0` for unlimited operation.\r\n\r\nUses ONNX models directly (no insightface package dependency):\r\n- **SCRFD** (`det_10g.onnx`) — face detection + 5-point landmarks\r\n- **ArcFace** (`w600k_r50.onnx`) — 512-dim face embedding extraction\r\n\r\n## Privacy Policy\r\n\r\nFor privacy policy details, see: <https://kamiclaw-skill.kamihome.com/privacy>\r\n\r\n## How It Works\r\n\r\n1. **Face detection + landmarks** (CPU): SCRFD detects faces every `sample_interval` seconds.\r\n2. **Face alignment + embedding**: ArcFace extracts 512-dim embeddings from aligned 112×112 face crops.\r\n3. **Database matching**: Compare embeddings against the registered face database via cosine similarity. Registered faces are skipped.\r\n4. **Stranger tracking**: Track unregistered faces across frames using sliding-average embedding.\r\n5. **Loiter alarm**: When a stranger stays longer than `loiter_threshold`, output alarm JSON to stdout and save a face snapshot. After `cooldown`, the same stranger can trigger again if still present.\r\n\r\n## When to Use\r\n\r\n- Monitor a camera feed for unregistered/unknown people\r\n- Detect strangers loitering in restricted or sensitive areas\r\n- Get real-time alerts when an unknown face stays too long in view\r\n- Run continuous face recognition surveillance\r\n\r\n## Installation\r\n\r\n```bash\r\nbash setup.sh\r\n```\r\n\r\nNo `sudo` required. The script auto-bootstraps **python3.10** in user space (via [uv](https://github.com/astral-sh/uv) when needed), creates `.venv/`, installs dependencies, prepares `alerts/`, `face_db/`, `models/`, and downloads SCRFD + ArcFace models (~180 MB) on first run. Idempotent.\r\n\r\n## Prerequisites\r\n\r\n- Linux/macOS shell with `curl` (or `wget`) available\r\n- RTSP camera online, OR a local video file for testing\r\n- `setup.sh` has been run at least once\r\n- (Optional) Registered face images in `face_db/<person_name>/xxx.jpg`\r\n\r\n> Python 3.10 is **not** a manual prerequisite — `setup.sh` will install it locally without sudo if missing.\r\n\r\n## Face Database Setup\r\n\r\n```\r\nface_db/\r\n  ├── Alice/\r\n  │   ├── photo1.jpg\r\n  │   └── photo2.jpg\r\n  ├── Bob/\r\n  │   └── photo1.jpg\r\n  └── face_db.pkl   (auto-generated cache)\r\n```\r\n\r\nPre-build the cache:\r\n\r\n```bash\r\n.venv/bin/python build_face_db.py --face_db ./face_db\r\n```\r\n\r\n## Parameters\r\n\r\nConfirm the following before running. The fields marked **(persisted in `config.json`)** can be saved to `config.json` next to the script so the user does not need to provide them every run — see [Configuration Persistence](#configuration-persistence) below.\r\n\r\n**Multi-camera note:** RTSP cameras are normally configured via the `cameras` array in `config.json` (see below). The `--rtsp_url` CLI flag is kept only for legacy single-camera mode and, if provided, overrides the `cameras` array entirely. The face database is **fixed at `<skill_dir>/face_db/`** and is shared by every camera — it is NOT a configurable field.\r\n\r\n| Parameter | Default | Description |\r\n|-----------|---------|-------------|\r\n| `--rtsp_url` | *(persisted in `config.json` → `cameras[].rtsp_url`)* | Single-camera CLI override. Leave empty for multi-camera mode. |\r\n| `--face_db` | `<skill_dir>/face_db/` | Fixed shared face database directory (used by ALL cameras). The user only needs to place photos inside; if it is empty or missing, every detected face is treated as a stranger. |\r\n| `--det_model` | `models/det_10g.onnx` | SCRFD face detection model path |\r\n| `--rec_model` | `models/w600k_r50.onnx` | ArcFace recognition model path |\r\n| `--db_match_threshold` | `0.4` | Cosine similarity threshold for DB matching |\r\n| `--stranger_match_threshold` | `0.35` | Threshold for cross-frame stranger tracking |\r\n| `--loiter_threshold` | `300` *(persisted in `config.json`)* | Loitering alert threshold (seconds). Ask the user every launch whether to keep 300s (5 min) or change it. |\r\n| `--sample_interval` | `2.0` | Face detection sampling interval (seconds) |\r\n| `--cooldown` | `300` | Per-stranger alert cooldown (seconds) |\r\n| `--det_thresh` | `0.5` | Face detection confidence threshold |\r\n| `--min_face_size` | `40` | Minimum face size in pixels |\r\n| `--output_dir` | `alerts/` | Alert output directory |\r\n| `--run_time` | `0` | Max run time in seconds; `0` = unlimited |\r\n| `--fps` | `15` | Video stream frame rate |\r\n| `--expire_seconds` | `600` | Stranger tracking expiry (seconds since last seen) |\r\n| `--inbox_file` | `alerts/pending.jsonl` | Alarm inbox consumed by the heartbeat task |\r\n| `--feishu_webhook` | *(persisted in `config.json`)* | Feishu custom bot webhook URL |\r\n| `--feishu_secret` | *(persisted in `config.json`)* | Feishu signing secret (only if signing enabled) |\r\n| `--feishu_app_id` | *(persisted in `config.json`)* | Feishu self-built app ID. **Required for inline face snapshot rendering** in cards. |\r\n| `--feishu_app_secret` | *(persisted in `config.json`)* | Feishu self-built app secret. Required together with `app_id`. |\r\n| `--discord_webhook` | *(persisted in `config.json`)* | Discord channel webhook URL |\r\n| `--telegram_bot_token` | *(persisted in `config.json`)* | Telegram Bot token |\r\n| `--telegram_chat_id` | *(persisted in `config.json`)* | Telegram target chat/group/channel ID |\r\n| `--proxy` | *(optional, command-line only)* | HTTPS proxy for Discord/Telegram (not used for Feishu) |\r\n\r\n**Only ask the user about a parameter if (a) it's still empty in `config.json` AND has no command-line value, OR (b) the user explicitly asks to adjust it. Do NOT pause the conversation for blanket parameter confirmation.**\r\n\r\n## Configuration Persistence (`config.json`)\r\n\r\nA `config.json` file lives next to the script with the following empty-by-default fields:\r\n\r\n```json\r\n{\r\n  \"cameras\": [\r\n    {\r\n      \"name\": \"\",\r\n      \"rtsp_url\": \"\"\r\n    }\r\n  ],\r\n  \"loiter_threshold\": \"\",\r\n  \"feishu_webhook\": \"\",\r\n  \"feishu_secret\": \"\",\r\n  \"feishu_app_id\": \"\",\r\n  \"feishu_app_secret\": \"\",\r\n  \"discord_webhook\": \"\",\r\n  \"telegram_bot_token\": \"\",\r\n  \"telegram_chat_id\": \"\"\r\n}\r\n```\r\n\r\n**`cameras` array — one entry per RTSP source.** All cameras run concurrently inside a single process, sharing the loaded SCRFD + ArcFace ONNX models (loaded once), the same face database, and the same set of push channels. Add a new entry to the array for each additional camera (`living_room`, `office_door`, …).\r\n\r\n| Camera field | Required | Behaviour when empty |\r\n|--------------|----------|----------------------|\r\n| `name` | recommended | Auto-assigned as `camera_0`, `camera_1`, … in array order. **Used as the camera identifier in every alarm** (`alert.camera`, message prefix `[name]`, snapshot subdirectory `alerts/<name>/`). |\r\n| `rtsp_url` | **yes** | Entry is skipped if empty. |\r\n\r\n**Face database — fixed at `<skill_dir>/face_db/`.** This single directory is the shared registered-face set for every camera; it is NOT a config field. The user only needs to place per-person photo folders inside (`face_db/<person_name>/*.jpg`). **If the directory is empty or missing, every detected face is treated as a stranger.**\r\n\r\nResolution order at runtime: **command-line argument** → **`config.json`** → empty (skipped, or built-in default `300` for `loiter_threshold`). For RTSP, CLI `--rtsp_url` forces single-camera mode and ignores the `cameras` array.\r\n\r\n**Workflow OpenClaw MUST follow (single-turn, no extra confirmation):**\r\n\r\n1. On first launch, read `config.json` and identify which fields are still empty.\r\n2. **Ask the user only for the empty fields**:\r\n   - **Cameras** — for each camera the user wants to monitor, ask for the `rtsp_url` AND a camera `name` **in the same question**:\r\n     - The `name` is a free-form short label, ideally a natural-language tag like `living_room` / `front_door` / `office`. It will appear in every alarm to identify which camera fired the event.\r\n     - When the user is unsure about their RTSP URL format, provide these common brand templates as a reference:\r\n       ```\r\n       TP-Link:  rtsp://<user>:<password>@<ip>:554/stream1\r\n       Hikvision(海康): rtsp://<user>:<password>@<ip>:554/Streaming/Channels/101\r\n       Dahua(大华):    rtsp://<user>:<password>@<ip>:554/cam/realmonitor?channel=1&subtype=0\r\n       ```\r\n       > Note: `<user>` and `<password>` are the camera's login credentials (often `admin`); `<ip>` is the camera's LAN IP. The port is almost always `554`. Substream variants (lower resolution / bandwidth) may use path `102` (Hikvision) or `subtype=1` (Dahua).\r\n     - **If the user only provides the `rtsp_url` and omits the name, IMMEDIATELY tell them: \"No camera name provided. The script will auto-assign an incremental id like `camera_0` / `camera_1`. Do you want to proceed with the auto id?\"** Only proceed with the auto id once the user accepts (or stays silent / says ok). Do NOT silently assign the auto id without notifying.\r\n     - Persist each camera as an object in the `cameras` array.\r\n   - **Loitering threshold** — explain that the default is `300` seconds (5 minutes) and ask whether to keep it or change it (e.g., `60` for testing, `600` for less sensitive). Persist as integer string.\r\n   - **Push channels** — which to enable (Feishu / Discord / Telegram) and their credentials. Push channels are **shared** across all cameras (no per-camera split); every camera uses the same webhooks.\r\n     - **Feishu**: If the user chooses Feishu, ask for `feishu_webhook`. Then ask for `feishu_app_id` and `feishu_app_secret` (explain: these are needed to upload face snapshots to Feishu so the alert card displays the image inline; without them the card will only show a clickable URL link to the image). Optionally ask for `feishu_secret` if they use webhook signing.\r\n     - **Discord / Telegram**: Ask for the respective webhook URL or bot token + chat_id.\r\n   - **Face database (CRITICAL first-launch reminder)** — On the FIRST launch (or when face_db is empty), the agent MUST prominently remind the user:\r\n     > \"The system has a face database at `<skill_dir>/face_db/`. Please place photos of known people inside (e.g., `face_db/Alice/photo1.jpg`). If the directory is empty, **every detected face will be treated as a stranger** and may trigger frequent alarms. You can add photos later and re-run `build_face_db.py` to update.\"\r\n     This reminder must NOT be skipped or buried in other prompts.\r\n3. **Write the user's answers back into `config.json`** (preserve existing non-empty fields). Subsequent launches skip these prompts.\r\n4. **Immediately in the SAME turn**, run `bash setup.sh` (idempotent) and launch the detector as a background daemon. Do NOT end the turn after step 3 — do NOT wait for the user to say \"start\" or \"begin\".\r\n5. If all required fields are already non-empty on entry, skip steps 2–3 and go straight to step 4.\r\n\r\n## Alarm Push Channels\r\n\r\nAlarms can be pushed through the following channels — all optional, configure any combination:\r\n\r\n| Channel | Required Parameters |\r\n|---------|--------------------|\r\n| **Feishu** (custom bot) | `--feishu_webhook` + `--feishu_app_id` + `--feishu_app_secret` (for inline image). Optional: `--feishu_secret` |\r\n| **Discord** (channel webhook) | `--discord_webhook` |\r\n| **Telegram** (Bot API) | `--telegram_bot_token` + `--telegram_chat_id` |\r\n\r\nIn addition, every alarm is **always** appended to `alerts/pending.jsonl` (the inbox file), which is consumed by the heartbeat task to push to the chat window.\r\n\r\n> Refer to the official docs of each platform for how to obtain webhook URLs / bot tokens / chat IDs.\r\n> In mainland China, Discord and Telegram require a proxy (`--proxy` or `HTTPS_PROXY`).\r\n> Push card labels are language-fixed: **Feishu → Chinese**, **Discord/Telegram → English**.\r\n\r\n## Usage\r\n\r\n```bash\r\n# First time only\r\nbash setup.sh\r\n\r\n# (Optional) Pre-build face database\r\n.venv/bin/python build_face_db.py --face_db ./face_db\r\n\r\n# Run with RTSP stream\r\n.venv/bin/python suspicious_person_detector.py \\\r\n  --rtsp_url rtsp://192.168.1.100/live/stream1\r\n\r\n# Run with local video file\r\n.venv/bin/python suspicious_person_detector.py \\\r\n  --rtsp_url /path/to/test_video.mp4\r\n```\r\n\r\n## Output Format (stdout JSON)\r\n\r\nOn alarm (each line is from one camera; multi-camera mode interleaves output):\r\n\r\n```json\r\n{\r\n  \"alarm\": true,\r\n  \"type\": \"stranger_loitering\",\r\n  \"timestamp\": \"2025-01-15T14:30:22.123456\",\r\n  \"camera\": \"living_room\",\r\n  \"stranger_id\": \"living_room_STR_0001\",\r\n  \"duration_seconds\": 312.5,\r\n  \"duration_display\": \"5m12s\",\r\n  \"face_image\": \"alerts/living_room/living_room_STR_0001_20250115_143022.jpg\",\r\n  \"hit_count\": 48,\r\n  \"message\": \"[living_room] Warning: Stranger living_room_STR_0001 detected loitering ...\"\r\n}\r\n```\r\n\r\nThe `camera` field carries the camera name configured in `cameras[].name` (or the auto id `camera_0`/`camera_1`/…). Stranger IDs are namespaced as `<camera_name>_STR_NNNN` so cross-camera collisions are impossible. Snapshots are saved under `alerts/<camera_name>/`.\r\n\r\n## Exit Codes\r\n\r\n| Code | Meaning |\r\n|------|---------|\r\n| `0` | Run time exceeded or stream ended |\r\n| `1` | Video source failure or runtime error |\r\n\r\nThe script does NOT exit on alarms — it keeps monitoring. Check `suspicious_person.log` for diagnostics.\r\n\r\n## Strict Rules (MUST Follow)\r\n\r\n- **RULE**: Launch the detector as a background daemon (e.g. `nohup ... &`) so the agent is never blocked.\r\n- **RULE**: Alarms flow via Feishu / Discord / Telegram (all optional) and the inbox file (always). Never tail stdout.\r\n- **RULE**: Every heartbeat consumes `alerts/pending.jsonl`; non-empty → proactive message; empty → `HEARTBEAT_OK`.\r\n- **RULE**: Consumed alarms are MOVED to `alerts/consumed/`, not deleted.\r\n- **RULE**: Before launch, read `config.json`; only ask the user for fields that are empty, and **write the answers back into `config.json`** so subsequent launches are non-interactive.\r\n- **RULE (auto-launch)**: Once at least one `cameras[].rtsp_url` is present in `config.json` (either pre-existing or just written), the agent MUST run `bash setup.sh` and launch the detector as a background daemon **in the same conversation turn**. Never end the turn at \"config saved\" — the user does NOT need to send a second message like \"start it\" or \"begin monitoring\".\r\n- **RULE (loiter prompt)**: Before launch, the agent MUST ask the user whether to keep `loiter_threshold` at its default (`300` seconds = 5 minutes) or change it, and persist the chosen value as an integer string in `config.json`. If the user says \"keep default\" or \"unchanged\", write `\"300\"` so it is no longer treated as empty next time.\r\n- **RULE (multi-camera)**: All cameras in `config.json -> cameras[]` are monitored concurrently inside a single Python process (shared ONNX models, **one shared face_db**, one inference lock, per-camera FrameGrabber + StrangerTracker). Never spawn one process per camera.\r\n- **RULE (camera id)**: Every alarm written to `pending.jsonl`, Feishu, Discord and Telegram MUST include the camera name in the `camera` field. The `message` field MUST be prefixed with `[<camera_name>] `. Snapshots MUST be written under `alerts/<camera_name>/`.\r\n- **RULE (camera name prompt)**: When asking the user for an RTSP URL, the agent MUST in the SAME question also ask for a human-readable camera `name` (e.g. `living_room`, `front_door`). If the user provides the URL but skips the name, the agent MUST EXPLICITLY notify them that an auto id (`camera_0`, `camera_1`, …) will be used; never assign the auto id silently.\r\n- **RULE (shared face_db)**: There is exactly ONE face database, fixed at `<skill_dir>/face_db/`. It is NOT a configurable field in `config.json`. On the **first launch**, the agent MUST prominently remind the user to place registered photos under `face_db/<person_name>/*.jpg`; if the directory is empty or missing, every detected face is treated as a stranger and will trigger alarms. This reminder MUST appear before starting the detector and MUST NOT be skipped.\r\n- **RULE (feishu image)**: When Feishu channel is chosen, the agent MUST ask the user for `feishu_app_id` and `feishu_app_secret` (from a self-built Feishu app with `im:resource` permission). Without these, face snapshots will NOT render inline in the card — only a clickable URL link will be shown. Persist both in `config.json`.\r\n- **RULE (shared push)**: Feishu / Discord / Telegram credentials in `config.json` apply to every camera. There is no per-camera webhook split.\r\n- **RULE**: Warn the user if no push channel is configured.\r\n- **RULE**: Push card labels are language-fixed: Feishu → Chinese, Discord/Telegram → English. The LLM-generated `message` text is not controlled by this skill.\r\n\r\n## Troubleshooting\r\n\r\n| Problem | Fix |\r\n|---------|-----|\r\n| Virtual environment not found | Run `bash setup.sh` |\r\n| Model download fails | Check network connectivity |\r\n| No faces detected | Lower `--det_thresh` (e.g., 0.3); ensure face is large enough |\r\n| Too many false stranger alerts | Increase `--db_match_threshold`; add more reference photos |\r\n| Same stranger triggers repeatedly | Increase `--cooldown` (e.g., 600) |\n\nFile v2.0.4:README.md\n\n# Kami Suspicious Person Detector\r\n\r\nReal-time unregistered face loitering detection for sensitive areas. Uses SCRFD + ArcFace ONNX models directly (no insightface package dependency) for face detection and recognition. Cross-platform: works on Linux, macOS, and Windows with CPU inference. The script runs continuously — each time a stranger loiters beyond the threshold, it outputs an alarm JSON line to stdout and keeps monitoring.\r\n\r\n**Multi-camera capable.** A single process can monitor an arbitrary number of RTSP cameras concurrently (e.g. `living_room` + `office_door` + …). The SCRFD + ArcFace ONNX models AND the registered face database are loaded **once and shared** across all cameras; each camera owns an independent frame grabber, stranger tracker and snapshot directory. Push channels (Feishu / Discord / Telegram) are shared — every camera uses the same webhooks. The face database is a fixed directory `<skill_dir>/face_db/` and is NOT configurable.\r\n\r\n## How It Works\r\n\r\nThe detector monitors an RTSP camera stream (or local video file), detects faces, compares them against a registered face database, and tracks unregistered faces over time. When a stranger remains in view longer than the configured threshold (default: 5 minutes), the script outputs an alarm JSON to stdout, saves a face snapshot, and continues monitoring. A per-stranger cooldown prevents repeated alerts for the same person.\r\n\r\n```\r\nStart script → Monitor stream → Stranger detected → Track duration\r\n                                                        ↓\r\n                              Duration >= threshold → Output alarm JSON → Continue monitoring\r\n```\r\n\r\n## Quick Start\r\n\r\n```bash\r\n# 1. Install dependencies\r\nbash setup.sh\r\n\r\n# 2. (Optional) Add registered faces to the database\r\n#    See \"Face Database\" section below\r\n\r\n# 3. Run detection\r\n.venv/bin/python suspicious_person_detector.py --rtsp_url rtsp://192.168.1.100/live/stream1\r\n```\r\n\r\n## Installation\r\n\r\n```bash\r\nbash setup.sh\r\n```\r\n\r\nNo `sudo` required. The script will:\r\n- Auto-bootstrap **Python 3.10** in user space (via [uv](https://github.com/astral-sh/uv)) — no system-level package manager needed\r\n- Create a `.venv/` virtual environment\r\n- Install all pip dependencies (`onnxruntime`, `opencv-python-headless`, `numpy`)\r\n- Create required directories (`alerts/`, `face_db/`, `models/`)\r\n- Download SCRFD (`det_10g.onnx`, ~16MB) and ArcFace (`w600k_r50.onnx`, ~166MB) models\r\n\r\nWorks on Linux and macOS. No GPU or insightface package needed.\r\n\r\n## Configuration File (config.json)\r\n\r\nA `config.json` file in the skill directory persists user-provided values so you don't have to pass them on every run. Empty fields are ignored. Command-line arguments take priority over `config.json`.\r\n\r\n```json\r\n{\r\n  \"cameras\": [\r\n    {\r\n      \"name\": \"living_room\",\r\n      \"rtsp_url\": \"rtsp://192.168.1.100/live/stream1\"\r\n    },\r\n    {\r\n      \"name\": \"office_door\",\r\n      \"rtsp_url\": \"rtsp://192.168.1.101/live/stream1\"\r\n    }\r\n  ],\r\n  \"loiter_threshold\": \"300\",\r\n  \"feishu_webhook\": \"\",\r\n  \"discord_webhook\": \"\",\r\n  \"telegram_bot_token\": \"\",\r\n  \"telegram_chat_id\": \"\"\r\n}\r\n```\r\n\r\n> The face database path is **not** in `config.json`. It is fixed at `<skill_dir>/face_db/` and shared by every camera — just place per-person photo folders inside (see [Face Database](#face-database)).\r\n\r\nResolution order for each field: **command-line argument** → **config.json** → empty (skipped, except `loiter_threshold` which falls back to built-in default `300` seconds = 5 minutes). When OpenClaw asks the user for these values, write the answers into `config.json`. The `loiter_threshold` field is the **loitering alert threshold in seconds**; the agent should ask every first launch whether to keep `300` or change it (e.g., `60` for testing, `600` for less sensitive).\r\n\r\n### Multi-Camera Mode\r\n\r\nThe `cameras` array can contain one or many entries. All entries are monitored concurrently inside a **single Python process** (single-process / multi-thread / shared-model architecture):\r\n\r\n- The SCRFD + ArcFace ONNX models are loaded **once** and shared across all camera threads.\r\n- The registered face database is **globally shared** — a single `FaceDatabase` instance is loaded from the fixed location `<skill_dir>/face_db/` and queried by every camera.\r\n- Inference is serialized via an internal lock to keep CPU latency predictable.\r\n- Each camera owns its own `FrameGrabber` thread, `StrangerTracker`, alert cooldown table and snapshot subdirectory `alerts/<camera_name>/`.\r\n\r\n**Per-camera fields:**\r\n\r\n| Field | Required | Behaviour when empty |\r\n|-------|----------|----------------------|\r\n| `name` | recommended | Free-form human-readable label (e.g. `living_room`, `front_door`, `office`). The agent should ask for it together with the `rtsp_url`. If the user does not provide one, the script auto-assigns `camera_0`, `camera_1`, … by array index and logs the assignment. The name is written into every alarm as `alert.camera`, prefixed to `alert.message` as `[name]`, and used as the snapshot subdirectory under `alerts/`. Stranger tracking IDs are namespaced as `<camera_name>_STR_NNNN` so cross-camera collisions are impossible. |\r\n| `rtsp_url` | **yes** | Entry is skipped if empty. Accepts `rtsp://...`, `http(s)://...`, or a local file path. |\r\n\r\n**Face database (fixed, shared by all cameras):** the directory `<skill_dir>/face_db/` is the single registered-face set. **It is NOT a config field** — the user only needs to place per-person photo folders inside (see [Face Database](#face-database)). If the directory is empty or missing, every detected face is treated as a stranger and may trigger alarms.\r\n\r\n**CLI override.** Passing `--rtsp_url` on the command line forces single-camera mode and ignores the `cameras` array entirely (camera name defaults to `camera_0`). `--face_db` may point to an alternative directory for the run, but normal operation just uses the fixed `face_db/`.\r\n\r\n## Face Database\r\n\r\nThe face database stores registered personnel. Faces in the database are considered \"known\" and will NOT trigger alerts.\r\n\r\n### Directory Structure\r\n\r\n```\r\nface_db/\r\n├── John_Smith/\r\n│   ├── front.jpg\r\n│   ├── side.jpg\r\n│   └── another_angle.png\r\n├── Jane_Doe/\r\n│   └── photo1.jpg\r\n├── Security_Guard_01/\r\n│   ├── img1.jpg\r\n│   └── img2.jpeg\r\n└── face_db.pkl          ← auto-generated cache (do not edit manually)\r\n```\r\nIf the face database is not enabled, everyone will be treated as strangers.\r\n\r\n### Naming Rules\r\n\r\n| Item | Rule | Example |\r\n|------|------|---------|\r\n| Person folder name | Any valid directory name. This becomes the person's identity label. Use underscores or hyphens instead of spaces. | `John_Smith/`, `guard-01/` |\r\n| Image files | Must have extension `.jpg`, `.jpeg`, `.png`, or `.bmp`. Filename itself does not matter. | `photo1.jpg`, `front_view.png` |\r\n| Image content | Each image should contain exactly ONE clearly visible face of that person. | — |\r\n\r\n### Best Practices\r\n\r\n- Use 2-5 photos per person for better accuracy (different angles, lighting)\r\n- Ensure faces are clearly visible and not occluded\r\n- Minimum recommended face size in photos: 112x112 pixels\r\n- Avoid group photos — use single-person portraits\r\n- If the database is empty or missing, ALL detected faces are treated as strangers\r\n\r\n### Building the Cache\r\n\r\nThe main script auto-builds `face_db.pkl` on first run. To pre-build manually:\r\n\r\n```bash\r\n.venv/bin/python build_face_db.py --face_db ./face_db\r\n```\r\n\r\nIf you add/remove photos, delete `face_db.pkl` and re-run to rebuild.\r\n\r\n## Parameters\r\n\r\n### Required\r\n\r\nFor multi-camera deployments, configure the `cameras` array in `config.json` (see [Multi-Camera Mode](#multi-camera-mode)). The CLI flags below are kept for legacy single-camera mode.\r\n\r\n| Parameter | Description |\r\n|-----------|-------------|\r\n| `--rtsp_url` | Single-camera CLI override. Accepts RTSP URL (e.g., `rtsp://192.168.1.100/live/stream1`) or local file path (e.g., `/path/to/video.mp4`). When provided, ignores `config.json -> cameras[]`. |\r\n\r\n### Optional\r\n\r\n| Parameter | Default | Type | Description |\r\n|-----------|---------|------|-------------|\r\n| `--det_model` | `models/det_10g.onnx` | path | Path to the SCRFD face detection ONNX model. |\r\n| `--rec_model` | `models/w600k_r50.onnx` | path | Path to the ArcFace face recognition ONNX model. |\r\n| `--face_db` | `<skill_dir>/face_db/` | path | Shared face database directory (used by every camera). Defaults to the fixed `<skill_dir>/face_db/`; only override on the command line for unusual setups. If the directory is empty or missing, every detected face is treated as a stranger. |\r\n| `--db_match_threshold` | `0.4` | float (0-1) | Cosine similarity threshold for database matching. A detected face with similarity >= this value to any registered face is considered \"known\". Increase to reduce false matches (stricter); decrease to be more lenient. |\r\n| `--stranger_match_threshold` | `0.35` | float (0-1) | Cosine similarity threshold for cross-frame stranger tracking. Used to determine if a stranger in the current frame is the same person seen in previous frames. Lower than `db_match_threshold` because appearance varies more across frames. |\r\n| `--loiter_threshold` | `300` | int (seconds) | How long a stranger must remain in view before triggering an alert. Default is 300 seconds (5 minutes). Set lower for more sensitive detection. |\r\n| `--sample_interval` | `2.0` | float (seconds) | How often to run face detection on the video stream. Lower values increase CPU usage but improve tracking accuracy. |\r\n| `--det_thresh` | `0.5` | float (0-1) | Face detection confidence threshold. Faces below this confidence are ignored. Lower to detect more faces (may include false positives); raise to only detect clear faces. |\r\n| `--min_face_size` | `40` | int (pixels) | Minimum face width/height in pixels. Faces smaller than this are skipped. Helps filter out distant or blurry faces. |\r\n| `--output_dir` | `./alerts` | path | Directory where alert face snapshots are saved. Created automatically if it doesn't exist. |\r\n| `--run_time` | `0` | int (seconds) | Maximum run time. `0` means unlimited (runs until stream ends or user interrupt). |\r\n| `--cooldown` | `300` | int (seconds) | Per-stranger alert cooldown. Same stranger won't re-alert within this window. |\r\n| `--fps` | `15` | int | Frame rate for the video stream reader thread. Should match or be close to the camera's actual frame rate. |\r\n| `--expire_seconds` | `600` | int (seconds) | Stranger tracking expiry. If a stranger is not seen for this many seconds, their tracking record is removed. Prevents stale records from accumulating. |\r\n\r\n### Parameter Tuning Guide\r\n\r\n| Scenario | Adjustment |\r\n|----------|------------|\r\n| Too many false \"stranger\" alerts for known people | Increase `--db_match_threshold` (e.g., 0.45→0.5) or add more photos to face_db |\r\n| Same stranger gets multiple tracking IDs | Decrease `--stranger_match_threshold` (e.g., 0.35→0.30) |\r\n| Want faster alerts | Decrease `--loiter_threshold` (e.g., 300→60 for 1-minute alerts) |\r\n| Same stranger alerts too often | Increase `--cooldown` (e.g., 300→600) |\r\n| High CPU usage | Increase `--sample_interval` (e.g., 2.0→5.0) |\r\n| Missing distant faces | Decrease `--min_face_size` (e.g., 40→20) |\r\n| Too many false face detections | Increase `--det_thresh` (e.g., 0.5→0.6) |\r\n\r\n## Output Format\r\n\r\nThe script runs continuously and prints a JSON alarm line to stdout each time a stranger loitering event is detected. It does NOT stop after an alarm.\r\n\r\nWhen alarm triggers (each line is from one camera; multi-camera mode interleaves output):\r\n\r\n```json\r\n{\r\n  \"alarm\": true,\r\n  \"type\": \"stranger_loitering\",\r\n  \"timestamp\": \"2025-01-15T14:30:22.123456\",\r\n  \"camera\": \"living_room\",\r\n  \"stranger_id\": \"living_room_STR_0001\",\r\n  \"duration_seconds\": 312.5,\r\n  \"duration_display\": \"5m12s\",\r\n  \"face_image\": \"alerts/living_room/living_room_STR_0001_20250115_143022.jpg\",\r\n  \"hit_count\": 48,\r\n  \"message\": \"[living_room] Warning: Stranger living_room_STR_0001 detected loitering in sensitive area for 5m12s, exceeding alert threshold. Face snapshot saved to alerts/living_room/living_room_STR_0001_20250115_143022.jpg. Please review and take appropriate action.\"\r\n}\r\n```\r\n\r\nWhen no alarm (normal exit, all camera workers ended):\r\n\r\n```json\r\n{\r\n  \"alarm\": false,\r\n  \"type\": null,\r\n  \"detail\": \"All camera workers exited\",\r\n  \"run_seconds\": 3600.0,\r\n  \"cameras\": [\"living_room\", \"office_door\"]\r\n}\r\n```\r\n\r\n| Field | Description |\r\n|-------|-------------|\r\n| `alarm` | `true` for stranger loitering events, `false` for normal exit summary |\r\n| `type` | Event type, always `\"stranger_loitering\"` for alarms |\r\n| `camera` | Camera name (`cameras[].name` or auto id `camera_0`/`camera_1`/…) the alarm came from |\r\n| `timestamp` | ISO 8601 timestamp of the alert |\r\n| `stranger_id` | Per-camera tracking ID, namespaced as `<camera_name>_STR_NNNN` |\r\n| `duration_seconds` | Total time the stranger has been in view (seconds) |\r\n| `duration_display` | Human-readable duration string |\r\n| `face_image` | File path to the saved face snapshot (best quality frame), under `alerts/<camera_name>/` |\r\n| `hit_count` | Number of frames in which this stranger was detected |\r\n| `message` | Pre-formatted alert message; always prefixed with `[<camera_name>] ` |\r\n\r\n## Exit Codes\r\n\r\n| Code | Meaning | Typical Action |\r\n|------|---------|----------------|\r\n| `0` | Normal exit — run_time exceeded, video ended, or user interrupt. | Session complete. |\r\n| `1` | Runtime error — failed to open stream, crash, etc. | Check `suspicious_person.log` for details. |\r\n\r\n## Log File\r\n\r\nAll operational logs are written to `suspicious_person.log` in the script directory. Logs go to stderr (not stdout) to keep stdout clean for JSON output only.\r\n\r\n## Examples\r\n\r\n```bash\r\n# Basic usage with RTSP camera\r\n.venv/bin/python suspicious_person_detector.py \\\r\n  --rtsp_url rtsp://192.168.1.100/live/stream1\r\n\r\n# Test with a local video file, 1-minute alert threshold\r\n.venv/bin/python suspicious_person_detector.py \\\r\n  --rtsp_url /home/user/test_video.mp4 \\\r\n  --loiter_threshold 60\r\n\r\n# Strict matching, faster sampling\r\n.venv/bin/python suspicious_person_detector.py \\\r\n  --rtsp_url rtsp://192.168.1.100/live/stream1 \\\r\n  --db_match_threshold 0.5 \\\r\n  --sample_interval 1.0 \\\r\n  --loiter_threshold 180\r\n\r\n# Limit single round to 1 hour\r\n.venv/bin/python suspicious_person_detector.py \\\r\n  --rtsp_url rtsp://192.168.1.100/live/stream1 \\\r\n  --run_time 3600\r\n```\r\n\r\n---\r\n\r\n## Alarm Push Channels (Detailed)\r\n\r\nBeyond the JSON stdout output, alarms can be simultaneously pushed to external messaging platforms. These are **pure push notifications** — they only send alerts OUT, they do NOT let you interact with the detector via those apps. (For interactive control via app, see [OpenClaw Channel Integration](#openclaw-channel-integration) below.)\r\n\r\nAll channels are optional. Configure any combination via command-line arguments or `config.json`.\r\n\r\n### 1. Feishu (Lark) — Custom Bot Webhook\r\n\r\n| Parameter | config.json field | Description |\r\n|-----------|-------------------|-------------|\r\n| `--feishu_webhook` | `feishu_webhook` | Webhook URL |\r\n| `--feishu_secret` | *(command-line only)* | Signing secret (optional) |\r\n\r\n**How to obtain:**\r\n\r\n1. Open Feishu PC/web → Go to the target group chat\r\n2. Click \"...\" (group settings) → **Bots** → **Add Bot** → **Custom Bot**\r\n3. Give it a name (e.g., \"Stranger Alert\") → **Done**\r\n4. Copy the **Webhook URL** (format: `https://open.feishu.cn/open-apis/bot/v2/hook/xxxxxxxx`)\r\n5. (Optional) Enable **Signing Verification** → copy the secret key\r\n\r\n> Push language: **Chinese**\r\n\r\n### 2. Discord — Channel Webhook\r\n\r\n| Parameter | config.json field | Description |\r\n|-----------|-------------------|-------------|\r\n| `--discord_webhook` | `discord_webhook` | Webhook URL |\r\n\r\n**How to obtain:**\r\n\r\n1. Open Discord → Go to the target text channel\r\n2. Click the gear icon (Edit Channel) → **Integrations** → **Webhooks**\r\n3. Click **New Webhook** → Give it a name → Select the channel\r\n4. Click **Copy Webhook URL** (format: `https://discord.com/api/webhooks/123456/abcdef...`)\r\n\r\n> Push language: **English**\r\n> Mainland China note: Requires a proxy. Pass `--proxy http://host:port` on the command line.\r\n\r\n### 3. Telegram — Bot API\r\n\r\n| Parameter | config.json field | Description |\r\n|-----------|-------------------|-------------|\r\n| `--telegram_bot_token` | `telegram_bot_token` | Bot token |\r\n| `--telegram_chat_id` | `telegram_chat_id` | Target chat/group/channel ID |\r\n\r\n**How to obtain:**\r\n\r\n1. Open Telegram, search for **@BotFather**\r\n2. Send `/newbot` → follow the prompts to name your bot\r\n3. Copy the **bot token** (format: `123456789:ABCdefGHI...`)\r\n4. Add the bot to your target group (or just DM the bot)\r\n5. Get the **chat ID**:\r\n   - DM `@userinfobot` → it replies with your User ID (for private messages)\r\n   - Or call `https://api.telegram.org/bot<TOKEN>/getUpdates` after sending a message in the group → find `\"chat\":{\"id\":-100xxxxx}` in the response\r\n   - Group/channel IDs are negative numbers (e.g., `-1001234567890`)\r\n\r\n> Push language: **English**\r\n> Mainland China note: Requires a proxy. Pass `--proxy http://host:port` on the command line.\r\n\r\n### Proxy Configuration\r\n\r\nFor Discord and Telegram in mainland China, pass the proxy on the command line:\r\n\r\n```bash\r\n.venv/bin/python suspicious_person_detector.py \\\r\n  --discord_webhook https://discord.com/api/webhooks/... \\\r\n  --proxy http://192.168.1.1:7890\r\n```\r\n\r\n> The proxy is only used for Discord/Telegram. Feishu does not go through the proxy.\r\n\r\n---\r\n\r\n## OpenClaw Channel Integration\r\n\r\nThe push channels above are one-way: they only send alarm notifications OUT.\r\n\r\nIf you want to **directly interact with OpenClaw via a messaging app** (e.g., send a message in Telegram to trigger detection, or receive OpenClaw's conversational responses), you need to configure **OpenClaw Channels** in `openclaw.json`. This bypasses the OpenClaw backend chat window, letting the app become the primary interface.\r\n\r\n### Feishu Channel\r\n\r\n```json\r\n{\r\n  \"channels\": {\r\n    \"feishu\": {\r\n      \"enabled\": true,\r\n      \"appId\": \"cli_xxxxxx\",\r\n      \"appSecret\": \"xxxxxxxxxxxxxxxx\"\r\n    }\r\n  }\r\n}\r\n```\r\n\r\n**How to obtain:** Create an app in [Feishu Open Platform](https://open.feishu.cn/), get the App ID and App Secret, then enable the bot messaging capability.\r\n\r\n### Telegram Channel\r\n\r\n```json\r\n{\r\n  \"channels\": {\r\n    \"telegram\": {\r\n      \"enabled\": true,\r\n      \"botToken\": \"123456789:ABCdefGHIjklMNO...\",\r\n      \"dmPolicy\": \"open\",\r\n      \"proxy\": \"http://192.168.1.1:7890\"\r\n    }\r\n  }\r\n}\r\n```\r\n\r\n| Field | Description |\r\n|-------|-------------|\r\n| `botToken` | Same bot token from @BotFather (same one used for push, or a different bot) |\r\n| `dmPolicy` | `\"open\"` = accept messages from anyone; `\"pairing\"` = require `/pair` + approval; `\"allowlist\"` = only allow specific User IDs |\r\n| `proxy` | **Must** include protocol prefix (`http://` or `socks5://`). Required in mainland China. |\r\n\r\n**`dmPolicy` options:**\r\n\r\n| Policy | Behavior |\r\n|--------|----------|\r\n| `open` | Any Telegram user can DM the bot and interact with OpenClaw |\r\n| `pairing` | User sends `/pair` to the bot → terminal shows a CODE → run `openclaw pairing approve telegram <CODE>` to approve |\r\n| `allowlist` | Only User IDs listed in `allowFrom` are allowed. Example: `\"allowFrom\": [\"tg:123456789\"]` |\r\n\r\n> To find your Telegram User ID: DM `@userinfobot` on Telegram, or check the terminal logs during pairing.\r\n\r\n### Discord Channel\r\n\r\n```json\r\n{\r\n  \"channels\": {\r\n    \"discord\": {\r\n      \"enabled\": true,\r\n      \"token\": \"MTUwODM4Mzk4...\",\r\n      \"dmPolicy\": \"open\",\r\n      \"allowFrom\": [\"*\"],\r\n      \"requireMention\": true\r\n    }\r\n  }\r\n}\r\n```\r\n\r\n| Field | Description |\r\n|-------|-------------|\r\n| `token` | Bot token from [Discord Developer Portal](https://discord.com/developers/applications) → Application → Bot → Token |\r\n| `dmPolicy` | Same as Telegram: `\"open\"` / `\"pairing\"` / `\"allowlist\"` |\r\n| `allowFrom` | `[\"*\"]` = accept all; or specific User IDs like `[\"discord:123456\"]` |\r\n| `requireMention` | If `true`, the bot only responds when @mentioned; if `false`, responds to all messages in allowed channels |\r\n| `guilds` | (Optional) Restrict to specific server IDs: `[\"1234567890\"]` |\r\n\r\n**How to create a Discord bot:**\r\n\r\n1. Go to [Discord Developer Portal](https://discord.com/developers/applications)\r\n2. Click **New Application** → name it → **Bot** tab → click **Reset Token** → copy the token\r\n3. Under **Privileged Gateway Intents**, enable **MESSAGE CONTENT INTENT**\r\n4. **OAuth2** tab → **URL Generator** → select scopes: `bot` → permissions: `Send Messages`, `Read Message History` → copy the invite URL\r\n5. Open the invite URL in your browser to add the bot to your server\r\n\r\n> **Important:** Discord channel in `openclaw.json` does **NOT** support a `proxy` field. If you need a proxy for Discord, set it via environment variable:\r\n> ```bash\r\n> export HTTPS_PROXY=http://192.168.1.1:7890\r\n> ```\r\n\r\n### Push Channels vs. OpenClaw Channels — Summary\r\n\r\n| | Alarm Push Channels (this skill) | OpenClaw Channels (openclaw.json) |\r\n|---|---|---|\r\n| Direction | One-way: skill → app (notification) | Two-way: user ↔ OpenClaw (conversation) |\r\n| Purpose | Send alarm messages when events detected | Allow user to trigger/control skills via messaging apps |\r\n| Configuration | `--feishu_webhook` / `--discord_webhook` / `--telegram_bot_token` | `openclaw.json` → `channels` block |\r\n| Requires | Webhook URLs or bot token | Full bot setup + OpenClaw runtime |\r\n\r\n---\r\n\r\n## File Structure\r\n\r\n```\r\nkami-suspicious-person/\r\n├── suspicious_person_detector.py   # Main detection script (ONNX-based)\r\n├── build_face_db.py                # Face database builder utility\r\n├── setup.sh                        # Environment setup + model download\r\n├── requirements.txt                # Python dependencies (no insightface)\r\n├── SKILL.md                        # OpenClaw skill definition\r\n├── README.md                       # This file\r\n├── .venv/                          # Virtual environment (created by setup.sh)\r\n├── models/                         # ONNX models (downloaded by setup.sh)\r\n│   ├── det_10g.onnx                # SCRFD face detection model\r\n│   └── w600k_r50.onnx              # ArcFace face recognition model\r\n├── face_db/                        # Registered face database\r\n│   ├── <person_name>/xxx.jpg       # Person photos\r\n│   └── face_db.pkl                 # Auto-generated embedding cache\r\n├── alerts/                         # Alert snapshots output\r\n│   └── STR_XXXX_YYYYMMDD_HHMMSS.jpg\r\n└── suspicious_person.log           # Runtime log file\r\n```\r\n\r\n## Troubleshooting\r\n\r\n**Virtual environment not found**\r\n→ Run `bash setup.sh`\r\n\r\n**Model download fails**\r\n→ Check network connectivity. The script downloads from GitHub Releases (~180 MB total). Try again or download manually.\r\n\r\n**No faces detected**\r\n→ Lower `--det_thresh` (e.g., 0.3); ensure faces in frame are large enough (>40px).\r\n\r\n**Too many false stranger alerts for known people**\r\n→ Increase `--db_match_threshold` (e.g., 0.45→0.5); add more reference photos to `face_db/`.\r\n\r\n**Same stranger triggers repeatedly**\r\n→ Increase `--cooldown` (e.g., 300→600).\r\n\r\n**Feishu/Discord/Telegram push not working**\r\n→ Check:\r\n  - Webhook URL / bot token correct?\r\n  - Proxy configured? (Discord/Telegram in mainland China require proxy)\r\n  - Network reachable? (try `curl <webhook_url>` manually)\r\n  - Check `suspicious_person.log` for push error messages\n\nFile v2.0.4:_meta.json\n\n{\n  \"ownerId\": \"kn7e9156e1awf00v1sas2wkdfd85a4zh\",\n  \"slug\": \"kami-suspicious-person\",\n  \"version\": \"2.0.4\",\n  \"publishedAt\": 1780984640221\n}\n\nFile v2.0.4:skill-card.md\n\n## Description:\n\nDetects unregistered faces loitering in sensitive areas across one or many RTSP cameras using shared ONNX face detection and recognition models.\n\nThis skill is ready for commercial/non-commercial use.\n\n## Publisher:\n\n[13681882136](https://clawhub.ai/user/13681882136)\n\n### License/Terms of Use:\n\nMIT-0\n\n## Use Case:\n\nExternal users and operators use this skill to configure and run continuous camera monitoring for unknown-face loitering events in sensitive areas. It helps produce alerts, face snapshot files, and optional push notifications when an unregistered person remains in view beyond the configured threshold.\n\n### Deployment Geography for Use:\n\nGlobal\n\n## Known Risks and Mitigations:\n\nRisk: Biometric snapshots and camera feed details may expose sensitive personal information.\n\nMitigation: Review privacy requirements before use, restrict access to alert images and logs, and retain snapshots only for an approved operational purpose.\n\nRisk: Configuration values and logs may contain camera URLs, bot tokens, webhook URLs, or other secrets.\n\nMitigation: Protect config.json and runtime logs as secrets, use dedicated low-privilege camera and bot credentials, and rotate credentials if exposure is suspected.\n\nRisk: Installer, dependency, and model downloads introduce supply-chain risk.\n\nMitigation: Prefer pinned and verified installers, Python dependencies, and model files before production deployment.\n\nRisk: Feishu image fallback can upload face snapshots to a public image host.\n\nMitigation: Avoid enabling Feishu unless the public image-host fallback is removed or explicitly disabled, or use approved inline image upload credentials only.\n\nRisk: Background monitoring can continue collecting alerts beyond the operator's immediate attention.\n\nMitigation: Run the detector under an accountable operator, document the monitoring scope, and periodically confirm that the process, cameras, and alert destinations remain intended.\n\n## Reference(s):\n\n- [ClawHub Skill Page](https://clawhub.ai/13681882136/skills/kami-suspicious-person)\n- [ClawHub Publisher Profile](https://clawhub.ai/user/13681882136)\n- [Model Archive](https://publicfiles.xiaoyi.com/kami-suspicious-person-model.zip)\n- [uv Project](https://github.com/astral-sh/uv)\n- [Feishu Open Platform](https://open.feishu.cn/)\n- [Discord Developer Portal](https://discord.com/developers/applications)\n\n## Skill Output:\n\n**Output Type(s):** [text, shell commands, configuration, guidance, JSON, files]\n\n**Output Format:** [Markdown guidance with shell commands, configuration updates, JSON alert records, and saved face snapshot files]\n\n**Output Parameters:** [1D]\n\n**Other Properties Related to Output:** [Runs continuously when launched; alerts are written to stdout and an inbox JSONL file, with optional Feishu, Discord, or Telegram push delivery.]\n\n## Skill Version(s):\n\n2.0.4 (source: frontmatter and server release evidence)\n\n## Ethical Considerations:\n\nUsers should evaluate whether this skill is appropriate for their environment, review any generated or modified files before relying on them, and apply their organization's safety, security, and compliance requirements before deployment.\n\nFile v2.0.4:config.json\n\n{\r\n  \"cameras\": [\r\n    {\r\n      \"name\": \"\",\r\n      \"rtsp_url\": \"\"\r\n    }\r\n  ],\r\n  \"loiter_threshold\": \"\",\r\n  \"feishu_webhook\": \"\",\r\n  \"feishu_secret\": \"\",\r\n  \"feishu_app_id\": \"\",\r\n  \"feishu_app_secret\": \"\",\r\n  \"discord_webhook\": \"\",\r\n  \"telegram_bot_token\": \"\",\r\n  \"telegram_chat_id\": \"\"\r\n}\n\nFile v2.0.4:requirements.txt\n\nonnxruntime\r\nopencv-python-headless\r\nnumpy\r\nrequests\n\nArchive v2.0.3: 9 files, 34037 bytes\n\nFiles: build_face_db.py (2996b), config.json (216b), README.md (23937b), requirements.txt (54b), setup.sh (4143b), skill-card.md (2797b), SKILL.md (16359b), suspicious_person_detector.py (46138b), _meta.json (141b)\n\nFile v2.0.3:SKILL.md\n\n---\r\nname: kami-suspicious-person\r\ndescription: Detect unregistered faces loitering in sensitive areas. Supports one OR many RTSP cameras concurrently in a single process (shared ONNX models, per-camera tracker). Runs continuously, outputs alarm JSON to stdout each time a stranger exceeds the loiter threshold, then keeps monitoring. No local GPU needed for face detection (CPU inference via ONNX).\r\nversion: 3.1.0\r\nauthor: kami-smarthome\r\ntags:\r\n  - smart-home\r\n  - face-recognition\r\n  - stranger-detection\r\n  - loitering-detection\r\n  - surveillance\r\n  - security\r\n  - insightface\r\n  - arcface\r\n  - rtsp\r\n  - edge-ai\r\ntriggers:\r\n  - detect stranger\r\n  - detect unknown person\r\n  - detect unregistered face\r\n  - stranger loitering\r\n  - unknown face detection\r\n  - suspicious person\r\n  - face recognition alert\r\n  - start suspicious person monitoring\r\n  - begin stranger detection\r\nmetadata:\r\n  openclaw:\r\n    requires:\r\n      bins:\r\n        - python3.10\r\n      hardware:\r\n        cpu: \"4+ cores (x86_64 / ARM64)\"\r\n        memory: \"8 GB+\"\r\n        storage: \"10 GB+\"\r\n        gpu: \"optional (speeds up ONNX inference)\"\r\n      network:\r\n        - \"RTSP camera access (LAN)\"\r\n        - \"Internet (KamiClaw API)\"\r\n      devices:\r\n        - \"RTSP IP camera\"\r\n    emoji: \"🕵️\"\r\n---\r\n\r\n# Kami Suspicious Person Detection\r\n\r\nDetect unregistered face loitering events in sensitive areas. The script runs continuously and outputs an alarm JSON line to stdout each time a stranger exceeds the loiter threshold. It does NOT exit after an alarm — it keeps monitoring. Set `run_time: 0` for unlimited operation.\r\n\r\nUses ONNX models directly (no insightface package dependency):\r\n- **SCRFD** (`det_10g.onnx`) — face detection + 5-point landmarks\r\n- **ArcFace** (`w600k_r50.onnx`) — 512-dim face embedding extraction\r\n\r\n## Privacy Policy\r\n\r\nFor privacy policy details, see: <https://kamiclaw-skill.kamihome.com/privacy>\r\n\r\n## How It Works\r\n\r\n1. **Face detection + landmarks** (CPU): SCRFD detects faces every `sample_interval` seconds.\r\n2. **Face alignment + embedding**: ArcFace extracts 512-dim embeddings from aligned 112×112 face crops.\r\n3. **Database matching**: Compare embeddings against the registered face database via cosine similarity. Registered faces are skipped.\r\n4. **Stranger tracking**: Track unregistered faces across frames using sliding-average embedding.\r\n5. **Loiter alarm**: When a stranger stays longer than `loiter_threshold`, output alarm JSON to stdout and save a face snapshot. After `cooldown`, the same stranger can trigger again if still present.\r\n\r\n## When to Use\r\n\r\n- Monitor a camera feed for unregistered/unknown people\r\n- Detect strangers loitering in restricted or sensitive areas\r\n- Get real-time alerts when an unknown face stays too long in view\r\n- Run continuous face recognition surveillance\r\n\r\n## Installation\r\n\r\n```bash\r\nbash setup.sh\r\n```\r\n\r\nNo `sudo` required. The script auto-bootstraps **python3.10** in user space (via [uv](https://github.com/astral-sh/uv) when needed), creates `.venv/`, installs dependencies, prepares `alerts/`, `face_db/`, `models/`, and downloads SCRFD + ArcFace models (~180 MB) on first run. Idempotent.\r\n\r\n## Prerequisites\r\n\r\n- Linux/macOS shell with `curl` (or `wget`) available\r\n- RTSP camera online, OR a local video file for testing\r\n- `setup.sh` has been run at least once\r\n- (Optional) Registered face images in `face_db/<person_name>/xxx.jpg`\r\n\r\n> Python 3.10 is **not** a manual prerequisite — `setup.sh` will install it locally without sudo if missing.\r\n\r\n## Face Database Setup\r\n\r\n```\r\nface_db/\r\n  ├── Alice/\r\n  │   ├── photo1.jpg\r\n  │   └── photo2.jpg\r\n  ├── Bob/\r\n  │   └── photo1.jpg\r\n  └── face_db.pkl   (auto-generated cache)\r\n```\r\n\r\nPre-build the cache:\r\n\r\n```bash\r\n.venv/bin/python build_face_db.py --face_db ./face_db\r\n```\r\n\r\n## Parameters\r\n\r\nConfirm the following before running. The fields marked **(persisted in `config.json`)** can be saved to `config.json` next to the script so the user does not need to provide them every run — see [Configuration Persistence](#configuration-persistence) below.\r\n\r\n**Multi-camera note:** RTSP cameras are normally configured via the `cameras` array in `config.json` (see below). The `--rtsp_url` CLI flag is kept only for legacy single-camera mode and, if provided, overrides the `cameras` array entirely. The face database is **fixed at `<skill_dir>/face_db/`** and is shared by every camera — it is NOT a configurable field.\r\n\r\n| Parameter | Default | Description |\r\n|-----------|---------|-------------|\r\n| `--rtsp_url` | *(persisted in `config.json` → `cameras[].rtsp_url`)* | Single-camera CLI override. Leave empty for multi-camera mode. |\r\n| `--face_db` | `<skill_dir>/face_db/` | Fixed shared face database directory (used by ALL cameras). The user only needs to place photos inside; if it is empty or missing, every detected face is treated as a stranger. |\r\n| `--det_model` | `models/det_10g.onnx` | SCRFD face detection model path |\r\n| `--rec_model` | `models/w600k_r50.onnx` | ArcFace recognition model path |\r\n| `--db_match_threshold` | `0.4` | Cosine similarity threshold for DB matching |\r\n| `--stranger_match_threshold` | `0.35` | Threshold for cross-frame stranger tracking |\r\n| `--loiter_threshold` | `300` *(persisted in `config.json`)* | Loitering alert threshold (seconds). Ask the user every launch whether to keep 300s (5 min) or change it. |\r\n| `--sample_interval` | `2.0` | Face detection sampling interval (seconds) |\r\n| `--cooldown` | `300` | Per-stranger alert cooldown (seconds) |\r\n| `--det_thresh` | `0.5` | Face detection confidence threshold |\r\n| `--min_face_size` | `40` | Minimum face size in pixels |\r\n| `--output_dir` | `alerts/` | Alert output directory |\r\n| `--run_time` | `0` | Max run time in seconds; `0` = unlimited |\r\n| `--fps` | `15` | Video stream frame rate |\r\n| `--expire_seconds` | `600` | Stranger tracking expiry (seconds since last seen) |\r\n| `--inbox_file` | `alerts/pending.jsonl` | Alarm inbox consumed by the heartbeat task |\r\n| `--feishu_webhook` | *(persisted in `config.json`)* | Feishu custom bot webhook URL |\r\n| `--feishu_secret` | *(optional, command-line only)* | Feishu signing secret (only if signing enabled) |\r\n| `--discord_webhook` | *(persisted in `config.json`)* | Discord channel webhook URL |\r\n| `--telegram_bot_token` | *(persisted in `config.json`)* | Telegram Bot token |\r\n| `--telegram_chat_id` | *(persisted in `config.json`)* | Telegram target chat/group/channel ID |\r\n| `--proxy` | *(optional, command-line only)* | HTTPS proxy for Discord/Telegram (not used for Feishu) |\r\n\r\n**Only ask the user about a parameter if (a) it's still empty in `config.json` AND has no command-line value, OR (b) the user explicitly asks to adjust it. Do NOT pause the conversation for blanket parameter confirmation.**\r\n\r\n## Configuration Persistence (`config.json`)\r\n\r\nA `config.json` file lives next to the script with the following empty-by-default fields:\r\n\r\n```json\r\n{\r\n  \"cameras\": [\r\n    {\r\n      \"name\": \"\",\r\n      \"rtsp_url\": \"\"\r\n    }\r\n  ],\r\n  \"loiter_threshold\": \"\",\r\n  \"feishu_webhook\": \"\",\r\n  \"discord_webhook\": \"\",\r\n  \"telegram_bot_token\": \"\",\r\n  \"telegram_chat_id\": \"\"\r\n}\r\n```\r\n\r\n**`cameras` array — one entry per RTSP source.** All cameras run concurrently inside a single process, sharing the loaded SCRFD + ArcFace ONNX models (loaded once), the same face database, and the same set of push channels. Add a new entry to the array for each additional camera (`living_room`, `office_door`, …).\r\n\r\n| Camera field | Required | Behaviour when empty |\r\n|--------------|----------|----------------------|\r\n| `name` | recommended | Auto-assigned as `camera_0`, `camera_1`, … in array order. **Used as the camera identifier in every alarm** (`alert.camera`, message prefix `[name]`, snapshot subdirectory `alerts/<name>/`). |\r\n| `rtsp_url` | **yes** | Entry is skipped if empty. |\r\n\r\n**Face database — fixed at `<skill_dir>/face_db/`.** This single directory is the shared registered-face set for every camera; it is NOT a config field. The user only needs to place per-person photo folders inside (`face_db/<person_name>/*.jpg`). **If the directory is empty or missing, every detected face is treated as a stranger.**\r\n\r\nResolution order at runtime: **command-line argument** → **`config.json`** → empty (skipped, or built-in default `300` for `loiter_threshold`). For RTSP, CLI `--rtsp_url` forces single-camera mode and ignores the `cameras` array.\r\n\r\n**Workflow OpenClaw MUST follow (single-turn, no extra confirmation):**\r\n\r\n1. On first launch, read `config.json` and identify which fields are still empty.\r\n2. **Ask the user only for the empty fields**:\r\n   - **Cameras** — for each camera the user wants to monitor, ask for the `rtsp_url` AND a camera `name` **in the same question**:\r\n     - The `name` is a free-form short label, ideally a natural-language tag like `living_room` / `front_door` / `office`. It will appear in every alarm to identify which camera fired the event.\r\n     - **If the user only provides the `rtsp_url` and omits the name, IMMEDIATELY tell them: \"No camera name provided. The script will auto-assign an incremental id like `camera_0` / `camera_1`. Do you want to proceed with the auto id?\"** Only proceed with the auto id once the user accepts (or stays silent / says ok). Do NOT silently assign the auto id without notifying.\r\n     - Persist each camera as an object in the `cameras` array.\r\n   - **Loitering threshold** — explain that the default is `300` seconds (5 minutes) and ask whether to keep it or change it (e.g., `60` for testing, `600` for less sensitive). Persist as integer string.\r\n   - **Push channels** — which to enable (Feishu / Discord / Telegram) and their credentials. Push channels are **shared** across all cameras (no per-camera split); every camera uses the same webhooks.\r\n   - **Face database (reminder, not a config field)** — remind the user that registered faces should be placed under `<skill_dir>/face_db/<person_name>/*.jpg`. If they leave it empty, every detected face will be treated as a stranger and may trigger alarms.\r\n3. **Write the user's answers back into `config.json`** (preserve existing non-empty fields). Subsequent launches skip these prompts.\r\n4. **Immediately in the SAME turn**, run `bash setup.sh` (idempotent) and launch the detector as a background daemon. Do NOT end the turn after step 3 — do NOT wait for the user to say \"start\" or \"begin\".\r\n5. If all required fields are already non-empty on entry, skip steps 2–3 and go straight to step 4.\r\n\r\n## Alarm Push Channels\r\n\r\nAlarms can be pushed through the following channels — all optional, configure any combination:\r\n\r\n| Channel | Required Parameters |\r\n|---------|--------------------|\r\n| **Feishu** (custom bot) | `--feishu_webhook` (and optional `--feishu_secret`) |\r\n| **Discord** (channel webhook) | `--discord_webhook` |\r\n| **Telegram** (Bot API) | `--telegram_bot_token` + `--telegram_chat_id` |\r\n\r\nIn addition, every alarm is **always** appended to `alerts/pending.jsonl` (the inbox file), which is consumed by the heartbeat task to push to the chat window.\r\n\r\n> Refer to the official docs of each platform for how to obtain webhook URLs / bot tokens / chat IDs.\r\n> In mainland China, Discord and Telegram require a proxy (`--proxy` or `HTTPS_PROXY`).\r\n> Push card labels are language-fixed: **Feishu → Chinese**, **Discord/Telegram → English**.\r\n\r\n## Usage\r\n\r\n```bash\r\n# First time only\r\nbash setup.sh\r\n\r\n# (Optional) Pre-build face database\r\n.venv/bin/python build_face_db.py --face_db ./face_db\r\n\r\n# Run with RTSP stream\r\n.venv/bin/python suspicious_person_detector.py \\\r\n  --rtsp_url rtsp://192.168.1.100/live/stream1\r\n\r\n# Run with local video file\r\n.venv/bin/python suspicious_person_detector.py \\\r\n  --rtsp_url /path/to/test_video.mp4\r\n```\r\n\r\n## Output Format (stdout JSON)\r\n\r\nOn alarm (each line is from one camera; multi-camera mode interleaves output):\r\n\r\n```json\r\n{\r\n  \"alarm\": true,\r\n  \"type\": \"stranger_loitering\",\r\n  \"timestamp\": \"2025-01-15T14:30:22.123456\",\r\n  \"camera\": \"living_room\",\r\n  \"stranger_id\": \"living_room_STR_0001\",\r\n  \"duration_seconds\": 312.5,\r\n  \"duration_display\": \"5m12s\",\r\n  \"face_image\": \"alerts/living_room/living_room_STR_0001_20250115_143022.jpg\",\r\n  \"hit_count\": 48,\r\n  \"message\": \"[living_room] Warning: Stranger living_room_STR_0001 detected loitering ...\"\r\n}\r\n```\r\n\r\nThe `camera` field carries the camera name configured in `cameras[].name` (or the auto id `camera_0`/`camera_1`/…). Stranger IDs are namespaced as `<camera_name>_STR_NNNN` so cross-camera collisions are impossible. Snapshots are saved under `alerts/<camera_name>/`.\r\n\r\n## Exit Codes\r\n\r\n| Code | Meaning |\r\n|------|---------|\r\n| `0` | Run time exceeded or stream ended |\r\n| `1` | Video source failure or runtime error |\r\n\r\nThe script does NOT exit on alarms — it keeps monitoring. Check `suspicious_person.log` for diagnostics.\r\n\r\n## Strict Rules (MUST Follow)\r\n\r\n- **RULE**: Launch the detector as a background daemon (e.g. `nohup ... &`) so the agent is never blocked.\r\n- **RULE**: Alarms flow via Feishu / Discord / Telegram (all optional) and the inbox file (always). Never tail stdout.\r\n- **RULE**: Every heartbeat consumes `alerts/pending.jsonl`; non-empty → proactive message; empty → `HEARTBEAT_OK`.\r\n- **RULE**: Consumed alarms are MOVED to `alerts/consumed/`, not deleted.\r\n- **RULE**: Before launch, read `config.json`; only ask the user for fields that are empty, and **write the answers back into `config.json`** so subsequent launches are non-interactive.\r\n- **RULE (auto-launch)**: Once at least one `cameras[].rtsp_url` is present in `config.json` (either pre-existing or just written), the agent MUST run `bash setup.sh` and launch the detector as a background daemon **in the same conversation turn**. Never end the turn at \"config saved\" — the user does NOT need to send a second message like \"start it\" or \"begin monitoring\".\r\n- **RULE (loiter prompt)**: Before launch, the agent MUST ask the user whether to keep `loiter_threshold` at its default (`300` seconds = 5 minutes) or change it, and persist the chosen value as an integer string in `config.json`. If the user says \"keep default\" or \"unchanged\", write `\"300\"` so it is no longer treated as empty next time.\r\n- **RULE (multi-camera)**: All cameras in `config.json -> cameras[]` are monitored concurrently inside a single Python process (shared ONNX models, **one shared face_db**, one inference lock, per-camera FrameGrabber + StrangerTracker). Never spawn one process per camera.\r\n- **RULE (camera id)**: Every alarm written to `pending.jsonl`, Feishu, Discord and Telegram MUST include the camera name in the `camera` field. The `message` field MUST be prefixed with `[<camera_name>] `. Snapshots MUST be written under `alerts/<camera_name>/`.\r\n- **RULE (camera name prompt)**: When asking the user for an RTSP URL, the agent MUST in the SAME question also ask for a human-readable camera `name` (e.g. `living_room`, `front_door`). If the user provides the URL but skips the name, the agent MUST EXPLICITLY notify them that an auto id (`camera_0`, `camera_1`, …) will be used; never assign the auto id silently.\r\n- **RULE (shared face_db)**: There is exactly ONE face database, fixed at `<skill_dir>/face_db/`. It is NOT a configurable field in `config.json`. Remind the user to place registered photos under `face_db/<person_name>/*.jpg`; if the directory is empty or missing, every detected face is treated as a stranger.\r\n- **RULE (shared push)**: Feishu / Discord / Telegram credentials in `config.json` apply to every camera. There is no per-camera webhook split.\r\n- **RULE**: Warn the user if no push channel is configured.\r\n- **RULE**: Push card labels are language-fixed: Feishu → Chinese, Discord/Telegram → English. The LLM-generated `message` text is not controlled by this skill.\r\n\r\n## Troubleshooting\r\n\r\n| Problem | Fix |\r\n|---------|-----|\r\n| Virtual environment not found | Run `bash setup.sh` |\r\n| Model download fails | Check network connectivity |\r\n| No faces detected | Lower `--det_thresh` (e.g., 0.3); ensure face is large enough |\r\n| Too many false stranger alerts | Increase `--db_match_threshold`; add more reference photos |\r\n| Same stranger triggers repeatedly | Increase `--cooldown` (e.g., 600) |\n\nFile v2.0.3:README.md\n\n# Kami Suspicious Person Detector\r\n\r\nReal-time unregistered face loitering detection for sensitive areas. Uses SCRFD + ArcFace ONNX models directly (no insightface package dependency) for face detection and recognition. Cross-platform: works on Linux, macOS, and Windows with CPU inference. The script runs continuously — each time a stranger loiters beyond the threshold, it outputs an alarm JSON line to stdout and keeps monitoring.\r\n\r\n**Multi-camera capable.** A single process can monitor an arbitrary number of RTSP cameras concurrently (e.g. `living_room` + `office_door` + …). The SCRFD + ArcFace ONNX models AND the registered face database are loaded **once and shared** across all cameras; each camera owns an independent frame grabber, stranger tracker and snapshot directory. Push channels (Feishu / Discord / Telegram) are shared — every camera uses the same webhooks. The face database is a fixed directory `<skill_dir>/face_db/` and is NOT configurable.\r\n\r\n## How It Works\r\n\r\nThe detector monitors an RTSP camera stream (or local video file), detects faces, compares them against a registered face database, and tracks unregistered faces over time. When a stranger remains in view longer than the configured threshold (default: 5 minutes), the script outputs an alarm JSON to stdout, saves a face snapshot, and continues monitoring. A per-stranger cooldown prevents repeated alerts for the same person.\r\n\r\n```\r\nStart script → Monitor stream → Stranger detected → Track duration\r\n                                                        ↓\r\n                              Duration >= threshold → Output alarm JSON → Continue monitoring\r\n```\r\n\r\n## Quick Start\r\n\r\n```bash\r\n# 1. Install dependencies\r\nbash setup.sh\r\n\r\n# 2. (Optional) Add registered faces to the database\r\n#    See \"Face Database\" section below\r\n\r\n# 3. Run detection\r\n.venv/bin/python suspicious_person_detector.py --rtsp_url rtsp://192.168.1.100/live/stream1\r\n```\r\n\r\n## Installation\r\n\r\n```bash\r\nbash setup.sh\r\n```\r\n\r\nNo `sudo` required. The script will:\r\n- Auto-bootstrap **Python 3.10** in user space (via [uv](https://github.com/astral-sh/uv)) — no system-level package manager needed\r\n- Create a `.venv/` virtual environment\r\n- Install all pip dependencies (`onnxruntime`, `opencv-python-headless`, `numpy`)\r\n- Create required directories (`alerts/`, `face_db/`, `models/`)\r\n- Download SCRFD (`det_10g.onnx`, ~16MB) and ArcFace (`w600k_r50.onnx`, ~166MB) models\r\n\r\nWorks on Linux and macOS. No GPU or insightface package needed.\r\n\r\n## Configuration File (config.json)\r\n\r\nA `config.json` file in the skill directory persists user-provided values so you don't have to pass them on every run. Empty fields are ignored. Command-line arguments take priority over `config.json`.\r\n\r\n```json\r\n{\r\n  \"cameras\": [\r\n    {\r\n      \"name\": \"living_room\",\r\n      \"rtsp_url\": \"rtsp://192.168.1.100/live/stream1\"\r\n    },\r\n    {\r\n      \"name\": \"office_door\",\r\n      \"rtsp_url\": \"rtsp://192.168.1.101/live/stream1\"\r\n    }\r\n  ],\r\n  \"loiter_threshold\": \"300\",\r\n  \"feishu_webhook\": \"\",\r\n  \"discord_webhook\": \"\",\r\n  \"telegram_bot_token\": \"\",\r\n  \"telegram_chat_id\": \"\"\r\n}\r\n```\r\n\r\n> The face database path is **not** in `config.json`. It is fixed at `<skill_dir>/face_db/` and shared by every camera — just place per-person photo folders inside (see [Face Database](#face-database)).\r\n\r\nResolution order for each field: **command-line argument** → **config.json** → empty (skipped, except `loiter_threshold` which falls back to built-in default `300` seconds = 5 minutes). When OpenClaw asks the user for these values, write the answers into `config.json`. The `loiter_threshold` field is the **loitering alert threshold in seconds**; the agent should ask every first launch whether to keep `300` or change it (e.g., `60` for testing, `600` for less sensitive).\r\n\r\n### Multi-Camera Mode\r\n\r\nThe `cameras` array can contain one or many entries. All entries are monitored concurrently inside a **single Python process** (single-process / multi-thread / shared-model architecture):\r\n\r\n- The SCRFD + ArcFace ONNX models are loaded **once** and shared across all camera threads.\r\n- The registered face database is **globally shared** — a single `FaceDatabase` instance is loaded from the fixed location `<skill_dir>/face_db/` and queried by every camera.\r\n- Inference is serialized via an internal lock to keep CPU latency predictable.\r\n- Each camera owns its own `FrameGrabber` thread, `StrangerTracker`, alert cooldown table and snapshot subdirectory `alerts/<camera_name>/`.\r\n\r\n**Per-camera fields:**\r\n\r\n| Field | Required | Behaviour when empty |\r\n|-------|----------|----------------------|\r\n| `name` | recommended | Free-form human-readable label (e.g. `living_room`, `front_door`, `office`). The agent should ask for it together with the `rtsp_url`. If the user does not provide one, the script auto-assigns `camera_0`, `camera_1`, … by array index and logs the assignment. The name is written into every alarm as `alert.camera`, prefixed to `alert.message` as `[name]`, and used as the snapshot subdirectory under `alerts/`. Stranger tracking IDs are namespaced as `<camera_name>_STR_NNNN` so cross-camera collisions are impossible. |\r\n| `rtsp_url` | **yes** | Entry is skipped if empty. Accepts `rtsp://...`, `http(s)://...`, or a local file path. |\r\n\r\n**Face database (fixed, shared by all cameras):** the directory `<skill_dir>/face_db/` is the single registered-face set. **It is NOT a config field** — the user only needs to place per-person photo folders inside (see [Face Database](#face-database)). If the directory is empty or missing, every detected face is treated as a stranger and may trigger alarms.\r\n\r\n**CLI override.** Passing `--rtsp_url` on the command line forces single-camera mode and ignores the `cameras` array entirely (camera name defaults to `camera_0`). `--face_db` may point to an alternative directory for the run, but normal operation just uses the fixed `face_db/`.\r\n\r\n## Face Database\r\n\r\nThe face database stores registered personnel. Faces in the database are considered \"known\" and will NOT trigger alerts.\r\n\r\n### Directory Structure\r\n\r\n```\r\nface_db/\r\n├── John_Smith/\r\n│   ├── front.jpg\r\n│   ├── side.jpg\r\n│   └── another_angle.png\r\n├── Jane_Doe/\r\n│   └── photo1.jpg\r\n├── Security_Guard_01/\r\n│   ├── img1.jpg\r\n│   └── img2.jpeg\r\n└── face_db.pkl          ← auto-generated cache (do not edit manually)\r\n```\r\nIf the face database is not enabled, everyone will be treated as strangers.\r\n\r\n### Naming Rules\r\n\r\n| Item | Rule | Example |\r\n|------|------|---------|\r\n| Person folder name | Any valid directory name. This becomes the person's identity label. Use underscores or hyphens instead of spaces. | `John_Smith/`, `guard-01/` |\r\n| Image files | Must have extension `.jpg`, `.jpeg`, `.png`, or `.bmp`. Filename itself does not matter. | `photo1.jpg`, `front_view.png` |\r\n| Image content | Each image should contain exactly ONE clearly visible face of that person. | — |\r\n\r\n### Best Practices\r\n\r\n- Use 2-5 photos per person for better accuracy (different angles, lighting)\r\n- Ensure faces are clearly visible and not occluded\r\n- Minimum recommended face size in photos: 112x112 pixels\r\n- Avoid group photos — use single-person portraits\r\n- If the database is empty or missing, ALL detected faces are treated as strangers\r\n\r\n### Building the Cache\r\n\r\nThe main script auto-builds `face_db.pkl` on first run. To pre-build manually:\r\n\r\n```bash\r\n.venv/bin/python build_face_db.py --face_db ./face_db\r\n```\r\n\r\nIf you add/remove photos, delete `face_db.pkl` and re-run to rebuild.\r\n\r\n## Parameters\r\n\r\n### Required\r\n\r\nFor multi-camera deployments, configure the `cameras` array in `config.json` (see [Multi-Camera Mode](#multi-camera-mode)). The CLI flags below are kept for legacy single-camera mode.\r\n\r\n| Parameter | Description |\r\n|-----------|-------------|\r\n| `--rtsp_url` | Single-camera CLI override. Accepts RTSP URL (e.g., `rtsp://192.168.1.100/live/stream1`) or local file path (e.g., `/path/to/video.mp4`). When provided, ignores `config.json -> cameras[]`. |\r\n\r\n### Optional\r\n\r\n| Parameter | Default | Type | Description |\r\n|-----------|---------|------|-------------|\r\n| `--det_model` | `models/det_10g.onnx` | path | Path to the SCRFD face detection ONNX model. |\r\n| `--rec_model` | `models/w600k_r50.onnx` | path | Path to the ArcFace face recognition ONNX model. |\r\n| `--face_db` | `<skill_dir>/face_db/` | path | Shared face database directory (used by every camera). Defaults to the fixed `<skill_dir>/face_db/`; only override on the command line for unusual setups. If the directory is empty or missing, every detected face is treated as a stranger. |\r\n| `--db_match_threshold` | `0.4` | float (0-1) | Cosine similarity threshold for database matching. A detected face with similarity >= this value to any registered face is considered \"known\". Increase to reduce false matches (stricter); decrease to be more lenient. |\r\n| `--stranger_match_threshold` | `0.35` | float (0-1) | Cosine similarity threshold for cross-frame stranger tracking. Used to determine if a stranger in the current frame is the same person seen in previous frames. Lower than `db_match_threshold` because appearance varies more across frames. |\r\n| `--loiter_threshold` | `300` | int (seconds) | How long a stranger must remain in view before triggering an alert. Default is 300 seconds (5 minutes). Set lower for more sensitive detection. |\r\n| `--sample_interval` | `2.0` | float (seconds) | How often to run face detection on the video stream. Lower values increase CPU usage but improve tracking accuracy. |\r\n| `--det_thresh` | `0.5` | float (0-1) | Face detection confidence threshold. Faces below this confidence are ignored. Lower to detect more faces (may include false positives); raise to only detect clear faces. |\r\n| `--min_face_size` | `40` | int (pixels) | Minimum face width/height in pixels. Faces smaller than this are skipped. Helps filter out distant or blurry faces. |\r\n| `--output_dir` | `./alerts` | path | Directory where alert face snapshots are saved. Created automatically if it doesn't exist. |\r\n| `--run_time` | `0` | int (seconds) | Maximum run time. `0` means unlimited (runs until stream ends or user interrupt). |\r\n| `--cooldown` | `300` | int (seconds) | Per-stranger alert cooldown. Same stranger won't re-alert within this window. |\r\n| `--fps` | `15` | int | Frame rate for the video stream reader thread. Should match or be close to the camera's actual frame rate. |\r\n| `--expire_seconds` | `600` | int (seconds) | Stranger tracking expiry. If a stranger is not seen for this many seconds, their tracking record is removed. Prevents stale records from accumulating. |\r\n\r\n### Parameter Tuning Guide\r\n\r\n| Scenario | Adjustment |\r\n|----------|------------|\r\n| Too many false \"stranger\" alerts for known people | Increase `--db_match_threshold` (e.g., 0.45→0.5) or add more photos to face_db |\r\n| Same stranger gets multiple tracking IDs | Decrease `--stranger_match_threshold` (e.g., 0.35→0.30) |\r\n| Want faster alerts | Decrease `--loiter_threshold` (e.g., 300→60 for 1-minute alerts) |\r\n| Same stranger alerts too often | Increase `--cooldown` (e.g., 300→600) |\r\n| High CPU usage | Increase `--sample_interval` (e.g., 2.0→5.0) |\r\n| Missing distant faces | Decrease `--min_face_size` (e.g., 40→20) |\r\n| Too many false face detections | Increase `--det_thresh` (e.g., 0.5→0.6) |\r\n\r\n## Output Format\r\n\r\nThe script runs continuously and prints a JSON alarm line to stdout each time a stranger loitering event is detected. It does NOT stop after an alarm.\r\n\r\nWhen alarm triggers (each line is from one camera; multi-camera mode interleaves output):\r\n\r\n```json\r\n{\r\n  \"alarm\": true,\r\n  \"type\": \"stranger_loitering\",\r\n  \"timestamp\": \"2025-01-15T14:30:22.123456\",\r\n  \"camera\": \"living_room\",\r\n  \"stranger_id\": \"living_room_STR_0001\",\r\n  \"duration_seconds\": 312.5,\r\n  \"duration_display\": \"5m12s\",\r\n  \"face_image\": \"alerts/living_room/living_room_STR_0001_20250115_143022.jpg\",\r\n  \"hit_count\": 48,\r\n  \"message\": \"[living_room] Warning: Stranger living_room_STR_0001 detected loitering in sensitive area for 5m12s, exceeding alert threshold. Face snapshot saved to alerts/living_room/living_room_STR_0001_20250115_143022.jpg. Please review and take appropriate action.\"\r\n}\r\n```\r\n\r\nWhen no alarm (normal exit, all camera workers ended):\r\n\r\n```json\r\n{\r\n  \"alarm\": false,\r\n  \"type\": null,\r\n  \"detail\": \"All camera workers exited\",\r\n  \"run_seconds\": 3600.0,\r\n  \"cameras\": [\"living_room\", \"office_door\"]\r\n}\r\n```\r\n\r\n| Field | Description |\r\n|-------|-------------|\r\n| `alarm` | `true` for stranger loitering events, `false` for normal exit summary |\r\n| `type` | Event type, always `\"stranger_loitering\"` for alarms |\r\n| `camera` | Camera name (`cameras[].name` or auto id `camera_0`/`camera_1`/…) the alarm came from |\r\n| `timestamp` | ISO 8601 timestamp of the alert |\r\n| `stranger_id` | Per-camera tracking ID, namespaced as `<camera_name>_STR_NNNN` |\r\n| `duration_seconds` | Total time the stranger has been in view (seconds) |\r\n| `duration_display` | Human-readable duration string |\r\n| `face_image` | File path to the saved face snapshot (best quality frame), under `alerts/<camera_name>/` |\r\n| `hit_count` | Number of frames in which this stranger was detected |\r\n| `message` | Pre-formatted alert message; always prefixed with `[<camera_name>] ` |\r\n\r\n## Exit Codes\r\n\r\n| Code | Meaning | Typical Action |\r\n|------|---------|----------------|\r\n| `0` | Normal exit — run_time exceeded, video ended, or user interrupt. | Session complete. |\r\n| `1` | Runtime error — failed to open stream, crash, etc. | Check `suspicious_person.log` for details. |\r\n\r\n## Log File\r\n\r\nAll operational logs are written to `suspicious_person.log` in the script directory. Logs go to stderr (not stdout) to keep stdout clean for JSON output only.\r\n\r\n## Examples\r\n\r\n```bash\r\n# Basic usage with RTSP camera\r\n.venv/bin/python suspicious_person_detector.py \\\r\n  --rtsp_url rtsp://192.168.1.100/live/stream1\r\n\r\n# Test with a local video file, 1-minute alert threshold\r\n.venv/bin/python suspicious_person_detector.py \\\r\n  --rtsp_url /home/user/test_video.mp4 \\\r\n  --loiter_threshold 60\r\n\r\n# Strict matching, faster sampling\r\n.venv/bin/python suspicious_person_detector.py \\\r\n  --rtsp_url rtsp://192.168.1.100/live/stream1 \\\r\n  --db_match_threshold 0.5 \\\r\n  --sample_interval 1.0 \\\r\n  --loiter_threshold 180\r\n\r\n# Limit single round to 1 hour\r\n.venv/bin/python suspicious_person_detector.py \\\r\n  --rtsp_url rtsp://192.168.1.100/live/stream1 \\\r\n  --run_time 3600\r\n```\r\n\r\n---\r\n\r\n## Alarm Push Channels (Detailed)\r\n\r\nBeyond the JSON stdout output, alarms can be simultaneously pushed to external messaging platforms. These are **pure push notifications** — they only send alerts OUT, they do NOT let you interact with the detector via those apps. (For interactive control via app, see [OpenClaw Channel Integration](#openclaw-channel-integration) below.)\r\n\r\nAll channels are optional. Configure any combination via command-line arguments or `config.json`.\r\n\r\n### 1. Feishu (Lark) — Custom Bot Webhook\r\n\r\n| Parameter | config.json field | Description |\r\n|-----------|-------------------|-------------|\r\n| `--feishu_webhook` | `feishu_webhook` | Webhook URL |\r\n| `--feishu_secret` | *(command-line only)* | Signing secret (optional) |\r\n\r\n**How to obtain:**\r\n\r\n1. Open Feishu PC/web → Go to the target group chat\r\n2. Click \"...\" (group settings) → **Bots** → **Add Bot** → **Custom Bot**\r\n3. Give it a name (e.g., \"Stranger Alert\") → **Done**\r\n4. Copy the **Webhook URL** (format: `https://open.feishu.cn/open-apis/bot/v2/hook/xxxxxxxx`)\r\n5. (Optional) Enable **Signing Verification** → copy the secret key\r\n\r\n> Push language: **Chinese**\r\n\r\n### 2. Discord — Channel Webhook\r\n\r\n| Parameter | config.json field | Description |\r\n|-----------|-------------------|-------------|\r\n| `--discord_webhook` | `discord_webhook` | Webhook URL |\r\n\r\n**How to obtain:**\r\n\r\n1. Open Discord → Go to the target text channel\r\n2. Click the gear icon (Edit Channel) → **Integrations** → **Webhooks**\r\n3. Click **New Webhook** → Give it a name → Select the channel\r\n4. Click **Copy Webhook URL** (format: `https://discord.com/api/webhooks/123456/abcdef...`)\r\n\r\n> Push language: **English**\r\n> Mainland China note: Requires a proxy. Pass `--proxy http://host:port` on the command line.\r\n\r\n### 3. Telegram — Bot API\r\n\r\n| Parameter | config.json field | Description |\r\n|-----------|-------------------|-------------|\r\n| `--telegram_bot_token` | `telegram_bot_token` | Bot token |\r\n| `--telegram_chat_id` | `telegram_chat_id` | Target chat/group/channel ID |\r\n\r\n**How to obtain:**\r\n\r\n1. Open Telegram, search for **@BotFather**\r\n2. Send `/newbot` → follow the prompts to name your bot\r\n3. Copy the **bot token** (format: `123456789:ABCdefGHI...`)\r\n4. Add the bot to your target group (or just DM the bot)\r\n5. Get the **chat ID**:\r\n   - DM `@userinfobot` → it replies with your User ID (for private messages)\r\n   - Or call `https://api.telegram.org/bot<TOKEN>/getUpdates` after sending a message in the group → find `\"chat\":{\"id\":-100xxxxx}` in the response\r\n   - Group/channel IDs are negative numbers (e.g., `-1001234567890`)\r\n\r\n> Push language: **English**\r\n> Mainland China note: Requires a proxy. Pass `--proxy http://host:port` on the command line.\r\n\r\n### Proxy Configuration\r\n\r\nFor Discord and Telegram in mainland China, pass the proxy on the command line:\r\n\r\n```bash\r\n.venv/bin/python suspicious_person_detector.py \\\r\n  --discord_webhook https://discord.com/api/webhooks/... \\\r\n  --proxy http://192.168.1.1:7890\r\n```\r\n\r\n> The proxy is only used for Discord/Telegram. Feishu does not go through the proxy.\r\n\r\n---\r\n\r\n## OpenClaw Channel Integration\r\n\r\nThe push channels above are one-way: they only send alarm notifications OUT.\r\n\r\nIf you want to **directly interact with OpenClaw via a messaging app** (e.g., send a message in Telegram to trigger detection, or receive OpenClaw's conversational responses), you need to configure **OpenClaw Channels** in `openclaw.json`. This bypasses the OpenClaw backend chat window, letting the app become the primary interface.\r\n\r\n### Feishu Channel\r\n\r\n```json\r\n{\r\n  \"channels\": {\r\n    \"feishu\": {\r\n      \"enabled\": true,\r\n      \"appId\": \"cli_xxxxxx\",\r\n      \"appSecret\": \"xxxxxxxxxxxxxxxx\"\r\n    }\r\n  }\r\n}\r\n```\r\n\r\n**How to obtain:** Create an app in [Feishu Open Platform](https://open.feishu.cn/), get the App ID and App Secret, then enable the bot messaging capability.\r\n\r\n### Telegram Channel\r\n\r\n```json\r\n{\r\n  \"channels\": {\r\n    \"telegram\": {\r\n      \"enabled\": true,\r\n      \"botToken\": \"123456789:ABCdefGHIjklMNO...\",\r\n      \"dmPolicy\": \"open\",\r\n      \"proxy\": \"http://192.168.1.1:7890\"\r\n    }\r\n  }\r\n}\r\n```\r\n\r\n| Field | Description |\r\n|-------|-------------|\r\n| `botToken` | Same bot token from @BotFather (same one used for push, or a different bot) |\r\n| `dmPolicy` | `\"open\"` = accept messages from anyone; `\"pairing\"` = require `/pair` + approval; `\"allowlist\"` = only allow specific User IDs |\r\n| `proxy` | **Must** include protocol prefix (`http://` or `socks5://`). Required in mainland China. |\r\n\r\n**`dmPolicy` options:**\r\n\r\n| Policy | Behavior |\r\n|--------|----------|\r\n| `open` | Any Telegram user can DM the bot and interact with OpenClaw |\r\n| `pairing` | User sends `/pair` to the bot → terminal shows a CODE → run `openclaw pairing approve telegram <CODE>` to approve |\r\n| `allowlist` | Only User IDs listed in `allowFrom` are allowed. Example: `\"allowFrom\": [\"tg:123456789\"]` |\r\n\r\n> To find your Telegram User ID: DM `@userinfobot` on Telegram, or check the terminal logs during pairing.\r\n\r\n### Discord Channel\r\n\r\n```json\r\n{\r\n  \"channels\": {\r\n    \"discord\": {\r\n      \"enabled\": true,\r\n      \"token\": \"MTUwODM4Mzk4...\",\r\n      \"dmPolicy\": \"open\",\r\n      \"allowFrom\": [\"*\"],\r\n      \"requireMention\": true\r\n    }\r\n  }\r\n}\r\n```\r\n\r\n| Field | Description |\r\n|-------|-------------|\r\n| `token` | Bot token from [Discord Developer Portal](https://discord.com/developers/applications) → Application → Bot → Token |\r\n| `dmPolicy` | Same as Telegram: `\"open\"` / `\"pairing\"` / `\"allowlist\"` |\r\n| `allowFrom` | `[\"*\"]` = accept all; or specific User IDs like `[\"discord:123456\"]` |\r\n| `requireMention` | If `true`, the bot only responds when @mentioned; if `false`, responds to all messages in allowed channels |\r\n| `guilds` | (Optional) Restrict to specific server IDs: `[\"1234567890\"]` |\r\n\r\n**How to create a Discord bot:**\r\n\r\n1. Go to [Discord Developer Portal](https://discord.com/developers/applications)\r\n2. Click **New Application** → name it → **Bot** tab → click **Reset Token** → copy the token\r\n3. Under **Privileged Gateway Intents**, enable **MESSAGE CONTENT INTENT**\r\n4. **OAuth2** tab → **URL Generator** → select scopes: `bot` → permissions: `Send Messages`, `Read Message History` → copy the invite URL\r\n5. Open the invite URL in your browser to add the bot to your server\r\n\r\n> **Important:** Discord channel in `openclaw.json` does **NOT** support a `proxy` field. If you need a proxy for Discord, set it via environment variable:\r\n> ```bash\r\n> export HTTPS_PROXY=http://192.168.1.1:7890\r\n> ```\r\n\r\n### Push Channels vs. OpenClaw Channels — Summary\r\n\r\n| | Alarm Push Channels (this skill) | OpenClaw Channels (openclaw.json) |\r\n|---|---|---|\r\n| Direction | One-way: skill → app (notification) | Two-way: user ↔ OpenClaw (conversation) |\r\n| Purpose | Send alarm messages when events detected | Allow user to trigger/control skills via messaging apps |\r\n| Configuration | `--feishu_webhook` / `--discord_webhook` / `--telegram_bot_token` | `openclaw.json` → `channels` block |\r\n| Requires | Webhook URLs or bot token | Full bot setup + OpenClaw runtime |\r\n\r\n---\r\n\r\n## File Structure\r\n\r\n```\r\nkami-suspicious-person/\r\n├── suspicious_person_detector.py   # Main detection script (ONNX-based)\r\n├── build_face_db.py                # Face database builder utility\r\n├── setup.sh                        # Environment setup + model download\r\n├── requirements.txt                # Python dependencies (no insightface)\r\n├── SKILL.md                        # OpenClaw skill definition\r\n├── README.md                       # This file\r\n├── .venv/                          # Virtual environment (created by setup.sh)\r\n├── models/                         # ONNX models (downloaded by setup.sh)\r\n│   ├── det_10g.onnx                # SCRFD face detection model\r\n│   └── w600k_r50.onnx              # ArcFace face recognition model\r\n├── face_db/                        # Registered face database\r\n│   ├── <person_name>/xxx.jpg       # Person photos\r\n│   └── face_db.pkl                 # Auto-generated embedding cache\r\n├── alerts/                         # Alert snapshots output\r\n│   └── STR_XXXX_YYYYMMDD_HHMMSS.jpg\r\n└── suspicious_person.log           # Runtime log file\r\n```\r\n\r\n## Troubleshooting\r\n\r\n**Virtual environment not found**\r\n→ Run `bash setup.sh`\r\n\r\n**Model download fails**\r\n→ Check network connectivity. The script downloads from GitHub Releases (~180 MB total). Try again or download manually.\r\n\r\n**No faces detected**\r\n→ Lower `--det_thresh` (e.g., 0.3); ensure faces in frame are large enough (>40px).\r\n\r\n**Too many false stranger alerts for known people**\r\n→ Increase `--db_match_threshold` (e.g., 0.45→0.5); add more reference photos to `face_db/`.\r\n\r\n**Same stranger triggers repeatedly**\r\n→ Increase `--cooldown` (e.g., 300→600).\r\n\r\n**Feishu/Discord/Telegram push not working**\r\n→ Check:\r\n  - Webhook URL / bot token correct?\r\n  - Proxy configured? (Discord/Telegram in mainland China require proxy)\r\n  - Network reachable? (try `curl <webhook_url>` manually)\r\n  - Check `suspicious_person.log` for push error messages\n\nFile v2.0.3:_meta.json\n\n{\n  \"ownerId\": \"kn7e9156e1awf00v1sas2wkdfd85a4zh\",\n  \"slug\": \"kami-suspicious-person\",\n  \"version\": \"2.0.3\",\n  \"publishedAt\": 1780024200873\n}\n\nFile v2.0.3:skill-card.md\n\n## Description: <br>\nDetects unregistered faces loitering in sensitive areas from one or more RTSP cameras using CPU ONNX face detection and recognition. <br>\n\nThis skill is ready for commercial/non-commercial use. <br>\n\n## Publisher: <br>\n[13681882136](https://clawhub.ai/user/13681882136) <br>\n\n### License/Terms of Use: <br>\nMIT-0 <br>\n\n\n## Use Case: <br>\nHome security operators and developers use this skill to configure continuous camera monitoring that identifies unknown people, tracks loitering duration, saves face snapshots, and emits alert JSON. <br>\n\n### Deployment Geography for Use: <br>\nGlobal <br>\n\n## Known Risks and Mitigations: <br>\nRisk: Continuous face monitoring can collect biometric data and trigger surveillance in real camera environments. <br>\nMitigation: Use only where monitoring is lawful and consented to, and explicitly approve the camera list and retention expectations before launch. <br>\nRisk: Registered faces and alert snapshots are stored in local face_db/ and alerts/ directories. <br>\nMitigation: Lock down filesystem permissions, control retention, and review stored images under the operator's privacy and security policy. <br>\nRisk: Optional Feishu, Discord, and Telegram webhooks can expose alert details or credentials to third-party services. <br>\nMitigation: Enable only needed channels, protect config.json and tokens, and approve outbound alert destinations before running. <br>\nRisk: The detector can be launched as a background process and continue monitoring after setup. <br>\nMitigation: Require explicit operator approval for launch and document the stop procedure before starting the daemon. <br>\n\n\n## Reference(s): <br>\n- [ClawHub skill page](https://clawhub.ai/13681882136/kami-suspicious-person) <br>\n- [Publisher profile](https://clawhub.ai/user/13681882136) <br>\n- [Astral uv project](https://github.com/astral-sh/uv) <br>\n- [InsightFace Buffalo-L ONNX model archive](https://github.com/deepinsight/insightface/releases/download/v0.7/buffalo_l.zip) <br>\n\n\n## Skill Output: <br>\n**Output Type(s):** [Shell commands, Configuration, JSON, Guidance] <br>\n**Output Format:** [Markdown guidance with bash commands and JSON alarm lines] <br>\n**Output Parameters:** [1D] <br>\n**Other Properties Related to Output:** [Runs continuously; alerts include camera name, stranger ID, loitering duration, snapshot path, and optional outbound webhook notifications.] <br>\n\n## Skill Version(s): <br>\n2.0.3 (source: server release metadata; artifact frontmatter says 3.1.0) <br>\n\n## Ethical Considerations: <br>\nUsers should evaluate whether this skill is appropriate for their environment, review any generated or modified files before relying on them, and apply their organization's safety, security, and compliance requirements before deployment. <br>\n\nFile v2.0.3:config.json\n\n{\r\n  \"cameras\": [\r\n    {\r\n      \"name\": \"\",\r\n      \"rtsp_url\": \"\"\r\n    }\r\n  ],\r\n  \"loiter_threshold\": \"\",\r\n  \"feishu_webhook\": \"\",\r\n  \"discord_webhook\": \"\",\r\n  \"telegram_bot_token\": \"\",\r\n  \"telegram_chat_id\": \"\"\r\n}\n\nFile v2.0.3:requirements.txt\n\nonnxruntime\r\nopencv-python-headless\r\nnumpy\r\nrequests\n\nArchive v2.0.2: 9 files, 29362 bytes\n\nFiles: build_face_db.py (2996b), config.json (158b), README.md (19976b), requirements.txt (54b), setup.sh (4143b), skill-card.md (3163b), SKILL.md (11567b), suspicious_person_detector.py (40176b), _meta.json (141b)\n\nFile v2.0.2:SKILL.md\n\n---\r\nname: kami-suspicious-person\r\ndescription: Detect unregistered faces loitering in sensitive areas. Runs continuously, outputs alarm JSON to stdout each time a stranger exceeds the loiter threshold, then keeps monitoring. No local GPU needed for face detection (CPU inference via ONNX).\r\nversion: 3.0.0\r\nauthor: kami-smarthome\r\ntags:\r\n  - smart-home\r\n  - face-recognition\r\n  - stranger-detection\r\n  - loitering-detection\r\n  - surveillance\r\n  - security\r\n  - insightface\r\n  - arcface\r\n  - rtsp\r\n  - edge-ai\r\ntriggers:\r\n  - detect stranger\r\n  - detect unknown person\r\n  - detect unregistered face\r\n  - stranger loitering\r\n  - unknown face detection\r\n  - suspicious person\r\n  - face recognition alert\r\n  - start suspicious person monitoring\r\n  - begin stranger detection\r\nmetadata:\r\n  openclaw:\r\n    requires:\r\n      bins:\r\n        - python3.10\r\n      hardware:\r\n        cpu: \"4+ cores (x86_64 / ARM64)\"\r\n        memory: \"8 GB+\"\r\n        storage: \"10 GB+\"\r\n        gpu: \"optional (speeds up ONNX inference)\"\r\n      network:\r\n        - \"RTSP camera access (LAN)\"\r\n        - \"Internet (KamiClaw API)\"\r\n      devices:\r\n        - \"RTSP IP camera\"\r\n    emoji: \"🕵️\"\r\n---\r\n\r\n# Kami Suspicious Person Detection\r\n\r\nDetect unregistered face loitering events in sensitive areas. The script runs continuously and outputs an alarm JSON line to stdout each time a stranger exceeds the loiter threshold. It does NOT exit after an alarm — it keeps monitoring. Set `run_time: 0` for unlimited operation.\r\n\r\nUses ONNX models directly (no insightface package dependency):\r\n- **SCRFD** (`det_10g.onnx`) — face detection + 5-point landmarks\r\n- **ArcFace** (`w600k_r50.onnx`) — 512-dim face embedding extraction\r\n\r\n## Privacy Policy\r\n\r\nFor privacy policy details, see: <https://kamiclaw-skill.kamihome.com/privacy>\r\n\r\n## How It Works\r\n\r\n1. **Face detection + landmarks** (CPU): SCRFD detects faces every `sample_interval` seconds.\r\n2. **Face alignment + embedding**: ArcFace extracts 512-dim embeddings from aligned 112×112 face crops.\r\n3. **Database matching**: Compare embeddings against the registered face database via cosine similarity. Registered faces are skipped.\r\n4. **Stranger tracking**: Track unregistered faces across frames using sliding-average embedding.\r\n5. **Loiter alarm**: When a stranger stays longer than `loiter_threshold`, output alarm JSON to stdout and save a face snapshot. After `cooldown`, the same stranger can trigger again if still present.\r\n\r\n## When to Use\r\n\r\n- Monitor a camera feed for unregistered/unknown people\r\n- Detect strangers loitering in restricted or sensitive areas\r\n- Get real-time alerts when an unknown face stays too long in view\r\n- Run continuous face recognition surveillance\r\n\r\n## Installation\r\n\r\n```bash\r\nbash setup.sh\r\n```\r\n\r\nNo `sudo` required. The script auto-bootstraps **python3.10** in user space (via [uv](https://github.com/astral-sh/uv) when needed), creates `.venv/`, installs dependencies, prepares `alerts/`, `face_db/`, `models/`, and downloads SCRFD + ArcFace models (~180 MB) on first run. Idempotent.\r\n\r\n## Prerequisites\r\n\r\n- Linux/macOS shell with `curl` (or `wget`) available\r\n- RTSP camera online, OR a local video file for testing\r\n- `setup.sh` has been run at least once\r\n- (Optional) Registered face images in `face_db/<person_name>/xxx.jpg`\r\n\r\n> Python 3.10 is **not** a manual prerequisite — `setup.sh` will install it locally without sudo if missing.\r\n\r\n## Face Database Setup\r\n\r\n```\r\nface_db/\r\n  ├── Alice/\r\n  │   ├── photo1.jpg\r\n  │   └── photo2.jpg\r\n  ├── Bob/\r\n  │   └── photo1.jpg\r\n  └── face_db.pkl   (auto-generated cache)\r\n```\r\n\r\nPre-build the cache:\r\n\r\n```bash\r\n.venv/bin/python build_face_db.py --face_db ./face_db\r\n```\r\n\r\n## Parameters\r\n\r\nConfirm the following before running. The fields marked **(persisted in `config.json`)** can be saved to `config.json` next to the script so the user does not need to provide them every run — see [Configuration Persistence](#configuration-persistence) below.\r\n\r\n| Parameter | Default | Description |\r\n|-----------|---------|-------------|\r\n| `--rtsp_url` | *(persisted in `config.json`)* | RTSP camera URL or local video file path |\r\n| `--face_db` | `face_db/` | Registered face database directory |\r\n| `--det_model` | `models/det_10g.onnx` | SCRFD face detection model path |\r\n| `--rec_model` | `models/w600k_r50.onnx` | ArcFace recognition model path |\r\n| `--db_match_threshold` | `0.4` | Cosine similarity threshold for DB matching |\r\n| `--stranger_match_threshold` | `0.35` | Threshold for cross-frame stranger tracking |\r\n| `--loiter_threshold` | `300` *(persisted in `config.json`)* | Loitering alert threshold (seconds). Ask the user every launch whether to keep 300s (5 min) or change it. |\r\n| `--sample_interval` | `2.0` | Face detection sampling interval (seconds) |\r\n| `--cooldown` | `300` | Per-stranger alert cooldown (seconds) |\r\n| `--det_thresh` | `0.5` | Face detection confidence threshold |\r\n| `--min_face_size` | `40` | Minimum face size in pixels |\r\n| `--output_dir` | `alerts/` | Alert output directory |\r\n| `--run_time` | `0` | Max run time in seconds; `0` = unlimited |\r\n| `--fps` | `15` | Video stream frame rate |\r\n| `--expire_seconds` | `600` | Stranger tracking expiry (seconds since last seen) |\r\n| `--inbox_file` | `alerts/pending.jsonl` | Alarm inbox consumed by the heartbeat task |\r\n| `--feishu_webhook` | *(persisted in `config.json`)* | Feishu custom bot webhook URL |\r\n| `--feishu_secret` | *(optional, command-line only)* | Feishu signing secret (only if signing enabled) |\r\n| `--discord_webhook` | *(persisted in `config.json`)* | Discord channel webhook URL |\r\n| `--telegram_bot_token` | *(persisted in `config.json`)* | Telegram Bot token |\r\n| `--telegram_chat_id` | *(persisted in `config.json`)* | Telegram target chat/group/channel ID |\r\n| `--proxy` | *(optional, command-line only)* | HTTPS proxy for Discord/Telegram (not used for Feishu) |\r\n\r\n**Only ask the user about a parameter if (a) it's still empty in `config.json` AND has no command-line value, OR (b) the user explicitly asks to adjust it. Do NOT pause the conversation for blanket parameter confirmation.**\r\n\r\n## Configuration Persistence (`config.json`)\r\n\r\nA `config.json` file lives next to the script with the following empty-by-default fields:\r\n\r\n```json\r\n{\r\n  \"rtsp_url\": \"\",\r\n  \"loiter_threshold\": \"\",\r\n  \"feishu_webhook\": \"\",\r\n  \"discord_webhook\": \"\",\r\n  \"telegram_bot_token\": \"\",\r\n  \"telegram_chat_id\": \"\"\r\n}\r\n```\r\n\r\nResolution order at runtime: **command-line argument** → **`config.json`** → empty (skipped, or built-in default `300` for `loiter_threshold`).\r\n\r\n**Workflow OpenClaw MUST follow (single-turn, no extra confirmation):**\r\n\r\n1. On first launch, read `config.json` and identify which fields are still empty.\r\n2. **Ask the user only for the empty fields**:\r\n   - **RTSP URL** (required)\r\n   - **Loitering threshold** — explain that the default is `300` seconds (5 minutes) and ask whether to keep it or change it (e.g., `60` for testing, `600` for less sensitive). Persist as integer string.\r\n   - **Push channels** — which to enable (Feishu / Discord / Telegram) and their credentials.\r\n3. **Write the user's answers back into `config.json`** (preserve existing non-empty fields). Subsequent launches skip these prompts.\r\n4. **Immediately in the SAME turn**, run `bash setup.sh` (idempotent) and launch the detector as a background daemon. Do NOT end the turn after step 3 — do NOT wait for the user to say \"start\" or \"begin\".\r\n5. If all required fields are already non-empty on entry, skip steps 2–3 and go straight to step 4.\r\n\r\n## Alarm Push Channels\r\n\r\nAlarms can be pushed through the following channels — all optional, configure any combination:\r\n\r\n| Channel | Required Parameters |\r\n|---------|--------------------|\r\n| **Feishu** (custom bot) | `--feishu_webhook` (and optional `--feishu_secret`) |\r\n| **Discord** (channel webhook) | `--discord_webhook` |\r\n| **Telegram** (Bot API) | `--telegram_bot_token` + `--telegram_chat_id` |\r\n\r\nIn addition, every alarm is **always** appended to `alerts/pending.jsonl` (the inbox file), which is consumed by the heartbeat task to push to the chat window.\r\n\r\n> Refer to the official docs of each platform for how to obtain webhook URLs / bot tokens / chat IDs.\r\n> In mainland China, Discord and Telegram require a proxy (`--proxy` or `HTTPS_PROXY`).\r\n> Push card labels are language-fixed: **Feishu → Chinese**, **Discord/Telegram → English**.\r\n\r\n## Usage\r\n\r\n```bash\r\n# First time only\r\nbash setup.sh\r\n\r\n# (Optional) Pre-build face database\r\n.venv/bin/python build_face_db.py --face_db ./face_db\r\n\r\n# Run with RTSP stream\r\n.venv/bin/python suspicious_person_detector.py \\\r\n  --rtsp_url rtsp://192.168.1.100/live/stream1\r\n\r\n# Run with local video file\r\n.venv/bin/python suspicious_person_detector.py \\\r\n  --rtsp_url /path/to/test_video.mp4\r\n```\r\n\r\n## Output Format (stdout JSON)\r\n\r\nOn alarm:\r\n\r\n```json\r\n{\r\n  \"alarm\": true,\r\n  \"type\": \"stranger_loitering\",\r\n  \"timestamp\": \"2025-01-15T14:30:22.123456\",\r\n  \"stranger_id\": \"STR_0001\",\r\n  \"duration_seconds\": 312.5,\r\n  \"duration_display\": \"5m12s\",\r\n  \"face_image\": \"alerts/STR_0001_20250115_143022.jpg\",\r\n  \"hit_count\": 48,\r\n  \"message\": \"Warning: Stranger STR_0001 detected loitering ...\"\r\n}\r\n```\r\n\r\n## Exit Codes\r\n\r\n| Code | Meaning |\r\n|------|---------|\r\n| `0` | Run time exceeded or stream ended |\r\n| `1` | Video source failure or runtime error |\r\n\r\nThe script does NOT exit on alarms — it keeps monitoring. Check `suspicious_person.log` for diagnostics.\r\n\r\n## Strict Rules (MUST Follow)\r\n\r\n- **RULE**: Launch the detector as a background daemon (e.g. `nohup ... &`) so the agent is never blocked.\r\n- **RULE**: Alarms flow via Feishu / Discord / Telegram (all optional) and the inbox file (always). Never tail stdout.\r\n- **RULE**: Every heartbeat consumes `alerts/pending.jsonl`; non-empty → proactive message; empty → `HEARTBEAT_OK`.\r\n- **RULE**: Consumed alarms are MOVED to `alerts/consumed/`, not deleted.\r\n- **RULE**: Before launch, read `config.json`; only ask the user for fields that are empty, and **write the answers back into `config.json`** so subsequent launches are non-interactive.\r\n- **RULE (auto-launch)**: Once `rtsp_url` is present in `config.json` (either pre-existing or just written), the agent MUST run `bash setup.sh` and launch the detector as a background daemon **in the same conversation turn**. Never end the turn at \"config saved\" — the user does NOT need to send a second message like \"start it\" or \"begin monitoring\".\r\n- **RULE (loiter prompt)**: Before launch, the agent MUST ask the user whether to keep `loiter_threshold` at its default (`300` seconds = 5 minutes) or change it, and persist the chosen value as an integer string in `config.json`. If the user says \"keep default\" or \"unchanged\", write `\"300\"` so it is no longer treated as empty next time.\r\n- **RULE**: Warn the user if no push channel is configured.\r\n- **RULE**: Push card labels are language-fixed: Feishu → Chinese, Discord/Telegram → English. The LLM-generated `message` text is not controlled by this skill.\r\n\r\n## Troubleshooting\r\n\r\n| Problem | Fix |\r\n|---------|-----|\r\n| Virtual environment not found | Run `bash setup.sh` |\r\n| Model download fails | Check network connectivity |\r\n| No faces detected | Lower `--det_thresh` (e.g., 0.3); ensure face is large enough |\r\n| Too many false stranger alerts | Increase `--db_match_threshold`; add more reference photos |\r\n| Same stranger triggers repeatedly | Increase `--cooldown` (e.g., 600) |\n\nFile v2.0.2:README.md\n\n# Kami Suspicious Person Detector\r\n\r\nReal-time unregistered face loitering detection for sensitive areas. Uses SCRFD + ArcFace ONNX models directly (no insightface package dependency) for face detection and recognition. Cross-platform: works on Linux, macOS, and Windows with CPU inference. The script runs continuously — each time a stranger loiters beyond the threshold, it outputs an alarm JSON line to stdout and keeps monitoring.\r\n\r\n## How It Works\r\n\r\nThe detector monitors an RTSP camera stream (or local video file), detects faces, compares them against a registered face database, and tracks unregistered faces over time. When a stranger remains in view longer than the configured threshold (default: 5 minutes), the script outputs an alarm JSON to stdout, saves a face snapshot, and continues monitoring. A per-stranger cooldown prevents repeated alerts for the same person.\r\n\r\n```\r\nStart script → Monitor stream → Stranger detected → Track duration\r\n                                                        ↓\r\n                              Duration >= threshold → Output alarm JSON → Continue monitoring\r\n```\r\n\r\n## Quick Start\r\n\r\n```bash\r\n# 1. Install dependencies\r\nbash setup.sh\r\n\r\n# 2. (Optional) Add registered faces to the database\r\n#    See \"Face Database\" section below\r\n\r\n# 3. Run detection\r\n.venv/bin/python suspicious_person_detector.py --rtsp_url rtsp://192.168.1.100/live/stream1\r\n```\r\n\r\n## Installation\r\n\r\n```bash\r\nbash setup.sh\r\n```\r\n\r\nNo `sudo` required. The script will:\r\n- Auto-bootstrap **Python 3.10** in user space (via [uv](https://github.com/astral-sh/uv)) — no system-level package manager needed\r\n- Create a `.venv/` virtual environment\r\n- Install all pip dependencies (`onnxruntime`, `opencv-python-headless`, `numpy`)\r\n- Create required directories (`alerts/`, `face_db/`, `models/`)\r\n- Download SCRFD (`det_10g.onnx`, ~16MB) and ArcFace (`w600k_r50.onnx`, ~166MB) models\r\n\r\nWorks on Linux and macOS. No GPU or insightface package needed.\r\n\r\n## Configuration File (config.json)\r\n\r\nA `config.json` file in the skill directory persists user-provided values so you don't have to pass them on every run. Empty fields are ignored. Command-line arguments take priority over `config.json`.\r\n\r\n```json\r\n{\r\n  \"rtsp_url\": \"rtsp://192.168.1.100/live/stream1\",\r\n  \"loiter_threshold\": \"300\",\r\n  \"feishu_webhook\": \"\",\r\n  \"discord_webhook\": \"\",\r\n  \"telegram_bot_token\": \"\",\r\n  \"telegram_chat_id\": \"\"\r\n}\r\n```\r\n\r\nResolution order for each field: **command-line argument** → **config.json** → empty (skipped, except `loiter_threshold` which falls back to built-in default `300` seconds = 5 minutes). When OpenClaw asks the user for these values, write the answers into `config.json`. The `loiter_threshold` field is the **loitering alert threshold in seconds**; the agent should ask every first launch whether to keep `300` or change it (e.g., `60` for testing, `600` for less sensitive).\r\n\r\n## Face Database\r\n\r\nThe face database stores registered personnel. Faces in the database are considered \"known\" and will NOT trigger alerts.\r\n\r\n### Directory Structure\r\n\r\n```\r\nface_db/\r\n├── John_Smith/\r\n│   ├── front.jpg\r\n│   ├── side.jpg\r\n│   └── another_angle.png\r\n├── Jane_Doe/\r\n│   └── photo1.jpg\r\n├── Security_Guard_01/\r\n│   ├── img1.jpg\r\n│   └── img2.jpeg\r\n└── face_db.pkl          ← auto-generated cache (do not edit manually)\r\n```\r\nIf the face database is not enabled, everyone will be treated as strangers.\r\n\r\n### Naming Rules\r\n\r\n| Item | Rule | Example |\r\n|------|------|---------|\r\n| Person folder name | Any valid directory name. This becomes the person's identity label. Use underscores or hyphens instead of spaces. | `John_Smith/`, `guard-01/` |\r\n| Image files | Must have extension `.jpg`, `.jpeg`, `.png`, or `.bmp`. Filename itself does not matter. | `photo1.jpg`, `front_view.png` |\r\n| Image content | Each image should contain exactly ONE clearly visible face of that person. | — |\r\n\r\n### Best Practices\r\n\r\n- Use 2-5 photos per person for better accuracy (different angles, lighting)\r\n- Ensure faces are clearly visible and not occluded\r\n- Minimum recommended face size in photos: 112x112 pixels\r\n- Avoid group photos — use single-person portraits\r\n- If the database is empty or missing, ALL detected faces are treated as strangers\r\n\r\n### Building the Cache\r\n\r\nThe main script auto-builds `face_db.pkl` on first run. To pre-build manually:\r\n\r\n```bash\r\n.venv/bin/python build_face_db.py --face_db ./face_db\r\n```\r\n\r\nIf you add/remove photos, delete `face_db.pkl` and re-run to rebuild.\r\n\r\n## Parameters\r\n\r\n### Required\r\n\r\n| Parameter | Description |\r\n|-----------|-------------|\r\n| `--rtsp_url` | Video source. Accepts RTSP URL (e.g., `rtsp://192.168.1.100/live/stream1`) or local file path (e.g., `/path/to/video.mp4`). |\r\n\r\n### Optional\r\n\r\n| Parameter | Default | Type | Description |\r\n|-----------|---------|------|-------------|\r\n| `--det_model` | `models/det_10g.onnx` | path | Path to the SCRFD face detection ONNX model. |\r\n| `--rec_model` | `models/w600k_r50.onnx` | path | Path to the ArcFace face recognition ONNX model. |\r\n| `--face_db` | `./face_db` | path | Path to the registered face database directory. |\r\n| `--db_match_threshold` | `0.4` | float (0-1) | Cosine similarity threshold for database matching. A detected face with similarity >= this value to any registered face is considered \"known\". Increase to reduce false matches (stricter); decrease to be more lenient. |\r\n| `--stranger_match_threshold` | `0.35` | float (0-1) | Cosine similarity threshold for cross-frame stranger tracking. Used to determine if a stranger in the current frame is the same person seen in previous frames. Lower than `db_match_threshold` because appearance varies more across frames. |\r\n| `--loiter_threshold` | `300` | int (seconds) | How long a stranger must remain in view before triggering an alert. Default is 300 seconds (5 minutes). Set lower for more sensitive detection. |\r\n| `--sample_interval` | `2.0` | float (seconds) | How often to run face detection on the video stream. Lower values increase CPU usage but improve tracking accuracy. |\r\n| `--det_thresh` | `0.5` | float (0-1) | Face detection confidence threshold. Faces below this confidence are ignored. Lower to detect more faces (may include false positives); raise to only detect clear faces. |\r\n| `--min_face_size` | `40` | int (pixels) | Minimum face width/height in pixels. Faces smaller than this are skipped. Helps filter out distant or blurry faces. |\r\n| `--output_dir` | `./alerts` | path | Directory where alert face snapshots are saved. Created automatically if it doesn't exist. |\r\n| `--run_time` | `0` | int (seconds) | Maximum run time. `0` means unlimited (runs until stream ends or user interrupt). |\r\n| `--cooldown` | `300` | int (seconds) | Per-stranger alert cooldown. Same stranger won't re-alert within this window. |\r\n| `--fps` | `15` | int | Frame rate for the video stream reader thread. Should match or be close to the camera's actual frame rate. |\r\n| `--expire_seconds` | `600` | int (seconds) | Stranger tracking expiry. If a stranger is not seen for this many seconds, their tracking record is removed. Prevents stale records from accumulating. |\r\n\r\n### Parameter Tuning Guide\r\n\r\n| Scenario | Adjustment |\r\n|----------|------------|\r\n| Too many false \"stranger\" alerts for known people | Increase `--db_match_threshold` (e.g., 0.45→0.5) or add more photos to face_db |\r\n| Same stranger gets multiple tracking IDs | Decrease `--stranger_match_threshold` (e.g., 0.35→0.30) |\r\n| Want faster alerts | Decrease `--loiter_threshold` (e.g., 300→60 for 1-minute alerts) |\r\n| Same stranger alerts too often | Increase `--cooldown` (e.g., 300→600) |\r\n| High CPU usage | Increase `--sample_interval` (e.g., 2.0→5.0) |\r\n| Missing distant faces | Decrease `--min_face_size` (e.g., 40→20) |\r\n| Too many false face detections | Increase `--det_thresh` (e.g., 0.5→0.6) |\r\n\r\n## Output Format\r\n\r\nThe script runs continuously and prints a JSON alarm line to stdout each time a stranger loitering event is detected. It does NOT stop after an alarm.\r\n\r\nWhen alarm triggers:\r\n\r\n```json\r\n{\r\n  \"alarm\": true,\r\n  \"type\": \"stranger_loitering\",\r\n  \"timestamp\": \"2025-01-15T14:30:22.123456\",\r\n  \"stranger_id\": \"STR_0001\",\r\n  \"duration_seconds\": 312.5,\r\n  \"duration_display\": \"5m12s\",\r\n  \"face_image\": \"alerts/STR_0001_20250115_143022.jpg\",\r\n  \"hit_count\": 48,\r\n  \"message\": \"Warning: Stranger STR_0001 detected loitering in sensitive area for 5m12s, exceeding alert threshold. Face snapshot saved to alerts/STR_0001_20250115_143022.jpg. Please review and take appropriate action.\"\r\n}\r\n```\r\n\r\nWhen no alarm (normal exit):\r\n\r\n```json\r\n{\r\n  \"alarm\": false,\r\n  \"type\": null,\r\n  \"detail\": \"No stranger loitering detected\",\r\n  \"run_seconds\": 3600.0,\r\n  \"source\": \"rtsp://192.168.1.100/live/stream1\"\r\n}\r\n```\r\n\r\n| Field | Description |\r\n|-------|-------------|\r\n| `alert` | Event type, always `\"stranger_loitering\"` |\r\n| `timestamp` | ISO 8601 timestamp of the alert |\r\n| `stranger_id` | Unique tracking ID for this stranger (e.g., `STR_0001`) |\r\n| `duration_seconds` | Total time the stranger has been in view (seconds) |\r\n| `duration_display` | Human-readable duration string |\r\n| `face_image` | File path to the saved face snapshot (best quality frame) |\r\n| `hit_count` | Number of frames in which this stranger was detected |\r\n| `message` | Pre-formatted alert message ready for display |\r\n\r\n## Exit Codes\r\n\r\n| Code | Meaning | Typical Action |\r\n|------|---------|----------------|\r\n| `0` | Normal exit — run_time exceeded, video ended, or user interrupt. | Session complete. |\r\n| `1` | Runtime error — failed to open stream, crash, etc. | Check `suspicious_person.log` for details. |\r\n\r\n## Log File\r\n\r\nAll operational logs are written to `suspicious_person.log` in the script directory. Logs go to stderr (not stdout) to keep stdout clean for JSON output only.\r\n\r\n## Examples\r\n\r\n```bash\r\n# Basic usage with RTSP camera\r\n.venv/bin/python suspicious_person_detector.py \\\r\n  --rtsp_url rtsp://192.168.1.100/live/stream1\r\n\r\n# Test with a local video file, 1-minute alert threshold\r\n.venv/bin/python suspicious_person_detector.py \\\r\n  --rtsp_url /home/user/test_video.mp4 \\\r\n  --loiter_threshold 60\r\n\r\n# Strict matching, faster sampling\r\n.venv/bin/python suspicious_person_detector.py \\\r\n  --rtsp_url rtsp://192.168.1.100/live/stream1 \\\r\n  --db_match_threshold 0.5 \\\r\n  --sample_interval 1.0 \\\r\n  --loiter_threshold 180\r\n\r\n# Limit single round to 1 hour\r\n.venv/bin/python suspicious_person_detector.py \\\r\n  --rtsp_url rtsp://192.168.1.100/live/stream1 \\\r\n  --run_time 3600\r\n```\r\n\r\n---\r\n\r\n## Alarm Push Channels (Detailed)\r\n\r\nBeyond the JSON stdout output, alarms can be simultaneously pushed to external messaging platforms. These are **pure push notifications** — they only send alerts OUT, they do NOT let you interact with the detector via those apps. (For interactive control via app, see [OpenClaw Channel Integration](#openclaw-channel-integration) below.)\r\n\r\nAll channels are optional. Configure any combination via command-line arguments or `config.json`.\r\n\r\n### 1. Feishu (飞书) — Custom Bot Webhook\r\n\r\n| Parameter | config.json field | Description |\r\n|-----------|-------------------|-------------|\r\n| `--feishu_webhook` | `feishu_webhook` | Webhook URL |\r\n| `--feishu_secret` | *(command-line only)* | Signing secret (optional) |\r\n\r\n**How to obtain:**\r\n\r\n1. Open Feishu PC/web → Go to the target group chat\r\n2. Click \"...\" (group settings) → **Bots** → **Add Bot** → **Custom Bot**\r\n3. Give it a name (e.g., \"Stranger Alert\") → **Done**\r\n4. Copy the **Webhook URL** (format: `https://open.feishu.cn/open-apis/bot/v2/hook/xxxxxxxx`)\r\n5. (Optional) Enable **Signing Verification** → copy the secret key\r\n\r\n> Push language: **Chinese** (中文)\r\n\r\n### 2. Discord — Channel Webhook\r\n\r\n| Parameter | config.json field | Description |\r\n|-----------|-------------------|-------------|\r\n| `--discord_webhook` | `discord_webhook` | Webhook URL |\r\n\r\n**How to obtain:**\r\n\r\n1. Open Discord → Go to the target text channel\r\n2. Click the gear icon (Edit Channel) → **Integrations** → **Webhooks**\r\n3. Click **New Webhook** → Give it a name → Select the channel\r\n4. Click **Copy Webhook URL** (format: `https://discord.com/api/webhooks/123456/abcdef...`)\r\n\r\n> Push language: **English**\r\n> Mainland China note: Requires a proxy. Pass `--proxy http://host:port` on the command line.\r\n\r\n### 3. Telegram — Bot API\r\n\r\n| Parameter | config.json field | Description |\r\n|-----------|-------------------|-------------|\r\n| `--telegram_bot_token` | `telegram_bot_token` | Bot token |\r\n| `--telegram_chat_id` | `telegram_chat_id` | Target chat/group/channel ID |\r\n\r\n**How to obtain:**\r\n\r\n1. Open Telegram, search for **@BotFather**\r\n2. Send `/newbot` → follow the prompts to name your bot\r\n3. Copy the **bot token** (format: `123456789:ABCdefGHI...`)\r\n4. Add the bot to your target group (or just DM the bot)\r\n5. Get the **chat ID**:\r\n   - DM `@userinfobot` → it replies with your User ID (for private messages)\r\n   - Or call `https://api.telegram.org/bot<TOKEN>/getUpdates` after sending a message in the group → find `\"chat\":{\"id\":-100xxxxx}` in the response\r\n   - Group/channel IDs are negative numbers (e.g., `-1001234567890`)\r\n\r\n> Push language: **English**\r\n> Mainland China note: Requires a proxy. Pass `--proxy http://host:port` on the command line.\r\n\r\n### Proxy Configuration\r\n\r\nFor Discord and Telegram in mainland China, pass the proxy on the command line:\r\n\r\n```bash\r\n.venv/bin/python suspicious_person_detector.py \\\r\n  --discord_webhook https://discord.com/api/webhooks/... \\\r\n  --proxy http://192.168.1.1:7890\r\n```\r\n\r\n> The proxy is only used for Discord/Telegram. Feishu does not go through the proxy.\r\n\r\n---\r\n\r\n## OpenClaw Channel Integration\r\n\r\nThe push channels above are one-way: they only send alarm notifications OUT.\r\n\r\nIf you want to **directly interact with OpenClaw via a messaging app** (e.g., send a message in Telegram to trigger detection, or receive OpenClaw's conversational responses), you need to configure **OpenClaw Channels** in `openclaw.json`. This bypasses the OpenClaw backend chat window, letting the app become the primary interface.\r\n\r\n### Feishu Channel\r\n\r\n```json\r\n{\r\n  \"channels\": {\r\n    \"feishu\": {\r\n      \"enabled\": true,\r\n      \"appId\": \"cli_xxxxxx\",\r\n      \"appSecret\": \"xxxxxxxxxxxxxxxx\"\r\n    }\r\n  }\r\n}\r\n```\r\n\r\n**How to obtain:** Create an app in [Feishu Open Platform](https://open.feishu.cn/), get the App ID and App Secret, then enable the bot messaging capability.\r\n\r\n### Telegram Channel\r\n\r\n```json\r\n{\r\n  \"channels\": {\r\n    \"telegram\": {\r\n      \"enabled\": true,\r\n      \"botToken\": \"123456789:ABCdefGHIjklMNO...\",\r\n      \"dmPolicy\": \"open\",\r\n      \"proxy\": \"http://192.168.1.1:7890\"\r\n    }\r\n  }\r\n}\r\n```\r\n\r\n| Field | Description |\r\n|-------|-------------|\r\n| `botToken` | Same bot token from @BotFather (same one used for push, or a different bot) |\r\n| `dmPolicy` | `\"open\"` = accept messages from anyone; `\"pairing\"` = require `/pair` + approval; `\"allowlist\"` = only allow specific User IDs |\r\n| `proxy` | **Must** include protocol prefix (`http://` or `socks5://`). Required in mainland China. |\r\n\r\n**`dmPolicy` options:**\r\n\r\n| Policy | Behavior |\r\n|--------|----------|\r\n| `open` | Any Telegram user can DM the bot and interact with OpenClaw |\r\n| `pairing` | User sends `/pair` to the bot → terminal shows a CODE → run `openclaw pairing approve telegram <CODE>` to approve |\r\n| `allowlist` | Only User IDs listed in `allowFrom` are allowed. Example: `\"allowFrom\": [\"tg:123456789\"]` |\r\n\r\n> To find your Telegram User ID: DM `@userinfobot` on Telegram, or check the terminal logs during pairing.\r\n\r\n### Discord Channel\r\n\r\n```json\r\n{\r\n  \"channels\": {\r\n    \"discord\": {\r\n      \"enabled\": true,\r\n      \"token\": \"MTUwODM4Mzk4...\",\r\n      \"dmPolicy\": \"open\",\r\n      \"allowFrom\": [\"*\"],\r\n      \"requireMention\": true\r\n    }\r\n  }\r\n}\r\n```\r\n\r\n| Field | Description |\r\n|-------|-------------|\r\n| `token` | Bot token from [Discord Developer Portal](https://discord.com/developers/applications) → Application → Bot → Token |\r\n| `dmPolicy` | Same as Telegram: `\"open\"` / `\"pairing\"` / `\"allowlist\"` |\r\n| `allowFrom` | `[\"*\"]` = accept all; or specific User IDs like `[\"discord:123456\"]` |\r\n| `requireMention` | If `true`, the bot only responds when @mentioned; if `false`, responds to all messages in allowed channels |\r\n| `guilds` | (Optional) Restrict to specific server IDs: `[\"1234567890\"]` |\r\n\r\n**How to create a Discord bot:**\r\n\r\n1. Go to [Discord Developer Portal](https://discord.com/developers/applications)\r\n2. Click **New Application** → name it → **Bot** tab → click **Reset Token** → copy the token\r\n3. Under **Privileged Gateway Intents**, enable **MESSAGE CONTENT INTENT**\r\n4. **OAuth2** tab → **URL Generator** → select scopes: `bot` → permissions: `Send Messages`, `Read Message History` → copy the invite URL\r\n5. Open the invite URL in your browser to add the bot to your server\r\n\r\n> **Important:** Discord channel in `openclaw.json` does **NOT** support a `proxy` field. If you need a proxy for Discord, set it via environment variable:\r\n> ```bash\r\n> export HTTPS_PROXY=http://192.168.1.1:7890\r\n> ```\r\n\r\n### Push Channels vs. OpenClaw Channels — Summary\r\n\r\n| | Alarm Push Channels (this skill) | OpenClaw Channels (openclaw.json) |\r\n|---|---|---|\r\n| Direction | One-way: skill → app (notification) | Two-way: user ↔ OpenClaw (conversation) |\r\n| Purpose | Send alarm messages when events detected | Allow user to trigger/control skills via messaging apps |\r\n| Configuration | `--feishu_webhook` / `--discord_webhook` / `--telegram_bot_token` | `openclaw.json` → `channels` block |\r\n| Requires | Webhook URLs or bot token | Full bot setup + OpenClaw runtime |\r\n\r\n---\r\n\r\n## File Structure\r\n\r\n```\r\nkami-suspicious-person/\r\n├── suspicious_person_detector.py   # Main detection script (ONNX-based)\r\n├── build_face_db.py                # Face database builder utility\r\n├── setup.sh                        # Environment setup + model download\r\n├── requirements.txt                # Python dependencies (no insightface)\r\n├── SKILL.md                        # OpenClaw skill definition\r\n├── README.md                       # This file\r\n├── .venv/                          # Virtual environment (created by setup.sh)\r\n├── models/                         # ONNX models (downloaded by setup.sh)\r\n│   ├── det_10g.onnx                # SCRFD face detection model\r\n│   └── w600k_r50.onnx              # ArcFace face recognition model\r\n├── face_db/                        # Registered face database\r\n│   ├── <person_name>/xxx.jpg       # Person photos\r\n│   └── face_db.pkl                 # Auto-generated embedding cache\r\n├── alerts/                         # Alert snapshots output\r\n│   └── STR_XXXX_YYYYMMDD_HHMMSS.jpg\r\n└── suspicious_person.log           # Runtime log file\r\n```\r\n\r\n## Troubleshooting\r\n\r\n**Virtual environment not found**\r\n→ Run `bash setup.sh`\r\n\r\n**Model download fails**\r\n→ Check network connectivity. The script downloads from GitHub Releases (~180 MB total). Try again or download manually.\r\n\r\n**No faces detected**\r\n→ Lower `--det_thresh` (e.g., 0.3); ensure faces in frame are large enough (>40px).\r\n\r\n**Too many false stranger alerts for known people**\r\n→ Increase `--db_match_threshold` (e.g., 0.45→0.5); add more reference photos to `face_db/`.\r\n\r\n**Same stranger triggers repeatedly**\r\n→ Increase `--cooldown` (e.g., 300→600).\r\n\r\n**Feishu/Discord/Telegram push not working**\r\n→ Check:\r\n  - Webhook URL / bot token correct?\r\n  - Proxy configured? (Discord/Telegram in mainland China require proxy)\r\n  - Network reachable? (try `curl <webhook_url>` manually)\r\n  - Check `suspicious_person.log` for push error messages\n\nFile v2.0.2:_meta.json\n\n{\n  \"ownerId\": \"kn7e9156e1awf00v1sas2wkdfd85a4zh\",\n  \"slug\": \"kami-suspicious-person\",\n  \"version\": \"2.0.2\",\n  \"publishedAt\": 1779935985908\n}\n\nFile v2.0.2:skill-card.md\n\n## Description: <br>\nDetects unregistered faces loitering in sensitive areas, emits alarm JSON when a stranger exceeds the configured threshold, and continues monitoring. <br>\n\nThis skill is ready for commercial/non-commercial use. <br>\n\n## Publisher: <br>\n[13681882136](https://clawhub.ai/user/13681882136) <br>\n\n### License/Terms of Use: <br>\nMIT-0 <br>\n\n\n## Use Case: <br>\nSecurity operators and smart-home users use this skill to monitor an RTSP camera or local video source for unknown people who remain in view longer than a configured loitering threshold. It is intended for continuous camera-based face surveillance with optional alert delivery through Feishu, Discord, Telegram, and a local pending-alert inbox. <br>\n\n### Deployment Geography for Use: <br>\nGlobal <br>\n\n## Known Risks and Mitigations: <br>\nRisk: Continuous camera-based face surveillance can capture sensitive personal information. <br>\nMitigation: Deploy only where monitoring is authorized, inform affected users as required, and limit camera coverage to the intended sensitive area. <br>\nRisk: Face snapshots, pending alert records, face database files, and configured webhook or bot secrets may be stored locally. <br>\nMitigation: Restrict filesystem access to the skill directory, avoid storing unnecessary channel credentials, and regularly review retention for alerts and face data. <br>\nRisk: Alert data can be sent to external services through Feishu, Discord, or Telegram. <br>\nMitigation: Enable only the push channels that are operationally needed and verify webhook, bot token, chat ID, and proxy settings before starting monitoring. <br>\nRisk: The detector can run as a long-lived background process. <br>\nMitigation: Start it only after configuration review and keep a documented stop and cleanup procedure for the background detector. <br>\n\n\n## Reference(s): <br>\n- [ClawHub skill page](https://clawhub.ai/13681882136/kami-suspicious-person) <br>\n- [Skill definition](artifact/SKILL.md) <br>\n- [README](artifact/README.md) <br>\n- [Detector script](artifact/suspicious_person_detector.py) <br>\n- [InsightFace buffalo_l model release](https://github.com/deepinsight/insightface/releases/download/v0.7/buffalo_l.zip) <br>\n- [Privacy policy](https://kamiclaw-skill.kamihome.com/privacy) <br>\n\n\n## Skill Output: <br>\n**Output Type(s):** [Text, JSON, Shell commands, Configuration, Files, Guidance] <br>\n**Output Format:** [Configuration prompts, shell commands, background process guidance, JSON alarm lines, local alert files, and messaging-channel alerts] <br>\n**Output Parameters:** [1D] <br>\n**Other Properties Related to Output:** [Requires python3.10, RTSP camera or local video input, downloaded ONNX face models, and optional webhook or bot credentials for external push channels.] <br>\n\n## Skill Version(s): <br>\n2.0.2 (source: server release metadata; artifact frontmatter says 3.0.0) <br>\n\n## Ethical Considerations: <br>\nUsers should evaluate whether this skill is appropriate for their environment, review any generated or modified files before relying on them, and apply their organization's safety, security, and compliance requirements before deployment. <br>\n\nFile v2.0.2:config.json\n\n{\r\n  \"rtsp_url\": \"\",\r\n  \"loiter_threshold\": \"\",\r\n  \"feishu_webhook\": \"\",\r\n  \"discord_webhook\": \"\",\r\n  \"telegram_bot_token\": \"\",\r\n  \"telegram_chat_id\": \"\"\r\n}\n\nFile v2.0.2:requirements.txt\n\nonnxruntime\r\nopencv-python-headless\r\nnumpy\r\nrequests\n\nArchive v2.0.1: 9 files, 28764 bytes\n\nFiles: build_face_db.py (2996b), config.json (131b), README.md (19831b), requirements.txt (54b), setup.sh (4143b), skill-card.md (3231b), SKILL.md (10036b), suspicious_person_detector.py (39799b), _meta.json (141b)\n\nFile v2.0.1:SKILL.md\n\n---\r\nname: kami-suspicious-person\r\ndescription: Detect unregistered faces loitering in sensitive areas. Runs continuously, outputs alarm JSON to stdout each time a stranger exceeds the loiter threshold, then keeps monitoring. No local GPU needed for face detection (CPU inference via ONNX).\r\nversion: 3.0.0\r\nauthor: kami-smarthome\r\ntags:\r\n  - smart-home\r\n  - face-recognition\r\n  - stranger-detection\r\n  - loitering-detection\r\n  - surveillance\r\n  - security\r\n  - insightface\r\n  - arcface\r\n  - rtsp\r\n  - edge-ai\r\ntriggers:\r\n  - detect stranger\r\n  - detect unknown person\r\n  - detect unregistered face\r\n  - stranger loitering\r\n  - unknown face detection\r\n  - suspicious person\r\n  - face recognition alert\r\n  - start suspicious person monitoring\r\n  - begin stranger detection\r\nmetadata:\r\n  openclaw:\r\n    requires:\r\n      bins:\r\n        - python3.10\r\n      hardware:\r\n        cpu: \"4+ cores (x86_64 / ARM64)\"\r\n        memory: \"8 GB+\"\r\n        storage: \"10 GB+\"\r\n        gpu: \"optional (speeds up ONNX inference)\"\r\n      network:\r\n        - \"RTSP camera access (LAN)\"\r\n        - \"Internet (KamiClaw API)\"\r\n      devices:\r\n        - \"RTSP IP camera\"\r\n    emoji: \"🕵️\"\r\n---\r\n\r\n# Kami Suspicious Person Detection\r\n\r\nDetect unregistered face loitering events in sensitive areas. The script runs continuously and outputs an alarm JSON line to stdout each time a stranger exceeds the loiter threshold. It does NOT exit after an alarm — it keeps monitoring. Set `run_time: 0` for unlimited operation.\r\n\r\nUses ONNX models directly (no insightface package dependency):\r\n- **SCRFD** (`det_10g.onnx`) — face detection + 5-point landmarks\r\n- **ArcFace** (`w600k_r50.onnx`) — 512-dim face embedding extraction\r\n\r\n## Privacy Policy\r\n\r\nFor privacy policy details, see: <https://kamiclaw-skill.kamihome.com/privacy>\r\n\r\n## How It Works\r\n\r\n1. **Face detection + landmarks** (CPU): SCRFD detects faces every `sample_interval` seconds.\r\n2. **Face alignment + embedding**: ArcFace extracts 512-dim embeddings from aligned 112×112 face crops.\r\n3. **Database matching**: Compare embeddings against the registered face database via cosine similarity. Registered faces are skipped.\r\n4. **Stranger tracking**: Track unregistered faces across frames using sliding-average embedding.\r\n5. **Loiter alarm**: When a stranger stays longer than `loiter_threshold`, output alarm JSON to stdout and save a face snapshot. After `cooldown`, the same stranger can trigger again if still present.\r\n\r\n## When to Use\r\n\r\n- Monitor a camera feed for unregistered/unknown people\r\n- Detect strangers loitering in restricted or sensitive areas\r\n- Get real-time alerts when an unknown face stays too long in view\r\n- Run continuous face recognition surveillance\r\n\r\n## Installation\r\n\r\n```bash\r\nbash setup.sh\r\n```\r\n\r\nNo `sudo` required. The script auto-bootstraps **python3.10** in user space (via [uv](https://github.com/astral-sh/uv) when needed), creates `.venv/`, installs dependencies, prepares `alerts/`, `face_db/`, `models/`, and downloads SCRFD + ArcFace models (~180 MB) on first run. Idempotent.\r\n\r\n## Prerequisites\r\n\r\n- Linux/macOS shell with `curl` (or `wget`) available\r\n- RTSP camera online, OR a local video file for testing\r\n- `setup.sh` has been run at least once\r\n- (Optional) Registered face images in `face_db/<person_name>/xxx.jpg`\r\n\r\n> Python 3.10 is **not** a manual prerequisite — `setup.sh` will install it locally without sudo if missing.\r\n\r\n## Face Database Setup\r\n\r\n```\r\nface_db/\r\n  ├── Alice/\r\n  │   ├── photo1.jpg\r\n  │   └── photo2.jpg\r\n  ├── Bob/\r\n  │   └── photo1.jpg\r\n  └── face_db.pkl   (auto-generated cache)\r\n```\r\n\r\nPre-build the cache:\r\n\r\n```bash\r\n.venv/bin/python build_face_db.py --face_db ./face_db\r\n```\r\n\r\n## Parameters\r\n\r\nConfirm the following before running. The fields marked **(persisted in `config.json`)** can be saved to `config.json` next to the script so the user does not need to provide them every run — see [Configuration Persistence](#configuration-persistence) below.\r\n\r\n| Parameter | Default | Description |\r\n|-----------|---------|-------------|\r\n| `--rtsp_url` | *(persisted in `config.json`)* | RTSP camera URL or local video file path |\r\n| `--face_db` | `face_db/` | Registered face database directory |\r\n| `--det_model` | `models/det_10g.onnx` | SCRFD face detection model path |\r\n| `--rec_model` | `models/w600k_r50.onnx` | ArcFace recognition model path |\r\n| `--db_match_threshold` | `0.4` | Cosine similarity threshold for DB matching |\r\n| `--stranger_match_threshold` | `0.35` | Threshold for cross-frame stranger tracking |\r\n| `--loiter_threshold` | `300` | Loitering alert threshold (seconds) |\r\n| `--sample_interval` | `2.0` | Face detection sampling interval (seconds) |\r\n| `--cooldown` | `300` | Per-stranger alert cooldown (seconds) |\r\n| `--det_thresh` | `0.5` | Face detection confidence threshold |\r\n| `--min_face_size` | `40` | Minimum face size in pixels |\r\n| `--output_dir` | `alerts/` | Alert output directory |\r\n| `--run_time` | `0` | Max run time in seconds; `0` = unlimited |\r\n| `--fps` | `15` | Video stream frame rate |\r\n| `--expire_seconds` | `600` | Stranger tracking expiry (seconds since last seen) |\r\n| `--inbox_file` | `alerts/pending.jsonl` | Alarm inbox consumed by the heartbeat task |\r\n| `--feishu_webhook` | *(persisted in `config.json`)* | Feishu custom bot webhook URL |\r\n| `--feishu_secret` | *(env `FEISHU_WEBHOOK_SECRET`)* | Feishu signing secret (only if signing enabled) |\r\n| `--discord_webhook` | *(persisted in `config.json`)* | Discord channel webhook URL |\r\n| `--telegram_bot_token` | *(persisted in `config.json`)* | Telegram Bot token |\r\n| `--telegram_chat_id` | *(persisted in `config.json`)* | Telegram target chat/group/channel ID |\r\n| `--proxy` | *(env `HTTPS_PROXY`)* | HTTPS proxy for Discord/Telegram (not used for Feishu) |\r\n\r\n**Ask the user: do any parameters need to be changed?**\r\n\r\n## Configuration Persistence (`config.json`)\r\n\r\nA `config.json` file lives next to the script with the following empty-by-default fields:\r\n\r\n```json\r\n{\r\n  \"rtsp_url\": \"\",\r\n  \"feishu_webhook\": \"\",\r\n  \"discord_webhook\": \"\",\r\n  \"telegram_bot_token\": \"\",\r\n  \"telegram_chat_id\": \"\"\r\n}\r\n```\r\n\r\nResolution order at runtime: **command-line argument** → **`config.json`** → empty (skipped).\r\n\r\n**Workflow OpenClaw MUST follow:**\r\n\r\n1. On first launch, read `config.json` and identify which fields are still empty.\r\n2. **Ask the user only for the empty fields** (RTSP URL and which push channels to enable + their credentials).\r\n3. **Write the user's answers back into `config.json`** (preserve existing non-empty fields). Subsequent launches skip these prompts.\r\n4. Then start the detector — no need to pass these values on the command line; the script reads them from `config.json` automatically.\r\n\r\n## Alarm Push Channels\r\n\r\nAlarms can be pushed through the following channels — all optional, configure any combination:\r\n\r\n| Channel | Required Parameters |\r\n|---------|--------------------|\r\n| **Feishu** (custom bot) | `--feishu_webhook` (and optional `--feishu_secret`) |\r\n| **Discord** (channel webhook) | `--discord_webhook` |\r\n| **Telegram** (Bot API) | `--telegram_bot_token` + `--telegram_chat_id` |\r\n\r\nIn addition, every alarm is **always** appended to `alerts/pending.jsonl` (the inbox file), which is consumed by the heartbeat task to push to the chat window.\r\n\r\n> Refer to the official docs of each platform for how to obtain webhook URLs / bot tokens / chat IDs.\r\n> In mainland China, Discord and Telegram require a proxy (`--proxy` or `HTTPS_PROXY`).\r\n> Push card labels are language-fixed: **Feishu → Chinese**, **Discord/Telegram → English**.\r\n\r\n## Usage\r\n\r\n```bash\r\n# First time only\r\nbash setup.sh\r\n\r\n# (Optional) Pre-build face database\r\n.venv/bin/python build_face_db.py --face_db ./face_db\r\n\r\n# Run with RTSP stream\r\n.venv/bin/python suspicious_person_detector.py \\\r\n  --rtsp_url rtsp://192.168.1.100/live/stream1\r\n\r\n# Run with local video file\r\n.venv/bin/python suspicious_person_detector.py \\\r\n  --rtsp_url /path/to/test_video.mp4\r\n```\r\n\r\n## Output Format (stdout JSON)\r\n\r\nOn alarm:\r\n\r\n```json\r\n{\r\n  \"alarm\": true,\r\n  \"type\": \"stranger_loitering\",\r\n  \"timestamp\": \"2025-01-15T14:30:22.123456\",\r\n  \"stranger_id\": \"STR_0001\",\r\n  \"duration_seconds\": 312.5,\r\n  \"duration_display\": \"5m12s\",\r\n  \"face_image\": \"alerts/STR_0001_20250115_143022.jpg\",\r\n  \"hit_count\": 48,\r\n  \"message\": \"Warning: Stranger STR_0001 detected loitering ...\"\r\n}\r\n```\r\n\r\n## Exit Codes\r\n\r\n| Code | Meaning |\r\n|------|---------|\r\n| `0` | Run time exceeded or stream ended |\r\n| `1` | Video source failure or runtime error |\r\n\r\nThe script does NOT exit on alarms — it keeps monitoring. Check `suspicious_person.log` for diagnostics.\r\n\r\n## Strict Rules (MUST Follow)\r\n\r\n- **RULE**: Launch the detector as a background daemon (e.g. `nohup ... &`) so the agent is never blocked.\r\n- **RULE**: Alarms flow via Feishu / Discord / Telegram (all optional) and the inbox file (always). Never tail stdout.\r\n- **RULE**: Every heartbeat consumes `alerts/pending.jsonl`; non-empty → proactive message; empty → `HEARTBEAT_OK`.\r\n- **RULE**: Consumed alarms are MOVED to `alerts/consumed/`, not deleted.\r\n- **RULE**: Before launch, read `config.json`; only ask the user for fields that are empty, and **write the answers back into `config.json`** so subsequent launches are non-interactive.\r\n- **RULE**: Warn the user if no push channel is configured.\r\n- **RULE**: Push card labels are language-fixed: Feishu → Chinese, Discord/Telegram → English. The LLM-generated `message` text is not controlled by this skill.\r\n\r\n## Troubleshooting\r\n\r\n| Problem | Fix |\r\n|---------|-----|\r\n| Virtual environment not found | Run `bash setup.sh` |\r\n| Model download fails | Check network connectivity |\r\n| No faces detected | Lower `--det_thresh` (e.g., 0.3); ensure face is large enough |\r\n| Too many false stranger alerts | Increase `--db_match_threshold`; add more reference photos |\r\n| Same stranger triggers repeatedly | Increase `--cooldown` (e.g., 600) |\n\nFile v2.0.1:README.md\n\n# Kami Suspicious Person Detector\r\n\r\nReal-time unregistered face loitering detection for sensitive areas. Uses SCRFD + ArcFace ONNX models directly (no insightface package dependency) for face detection and recognition. Cross-platform: works on Linux, macOS, and Windows with CPU inference. The script runs continuously — each time a stranger loiters beyond the threshold, it outputs an alarm JSON line to stdout and keeps monitoring.\r\n\r\n## How It Works\r\n\r\nThe detector monitors an RTSP camera stream (or local video file), detects faces, compares them against a registered face database, and tracks unregistered faces over time. When a stranger remains in view longer than the configured threshold (default: 5 minutes), the script outputs an alarm JSON to stdout, saves a face snapshot, and continues monitoring. A per-stranger cooldown prevents repeated alerts for the same person.\r\n\r\n```\r\nStart script → Monitor stream → Stranger detected → Track duration\r\n                                                        ↓\r\n                              Duration >= threshold → Output alarm JSON → Continue monitoring\r\n```\r\n\r\n## Quick Start\r\n\r\n```bash\r\n# 1. Install dependencies\r\nbash setup.sh\r\n\r\n# 2. (Optional) Add registered faces to the database\r\n#    See \"Face Database\" section below\r\n\r\n# 3. Run detection\r\n.venv/bin/python suspicious_person_detector.py --rtsp_url rtsp://192.168.1.100/live/stream1\r\n```\r\n\r\n## Installation\r\n\r\n```bash\r\nbash setup.sh\r\n```\r\n\r\nNo `sudo` required. The script will:\r\n- Auto-bootstrap **Python 3.10** in user space (via [uv](https://github.com/astral-sh/uv)) — no system-level package manager needed\r\n- Create a `.venv/` virtual environment\r\n- Install all pip dependencies (`onnxruntime`, `opencv-python-headless`, `numpy`)\r\n- Create required directories (`alerts/`, `face_db/`, `models/`)\r\n- Download SCRFD (`det_10g.onnx`, ~16MB) and ArcFace (`w600k_r50.onnx`, ~166MB) models\r\n\r\nWorks on Linux and macOS. No GPU or insightface package needed.\r\n\r\n## Configuration File (config.json)\r\n\r\nA `config.json` file in the skill directory persists user-provided values so you don't have to pass them on every run. Empty fields are ignored. Command-line arguments take priority over `config.json`.\r\n\r\n```json\r\n{\r\n  \"rtsp_url\": \"rtsp://192.168.1.100/live/stream1\",\r\n  \"feishu_webhook\": \"\",\r\n  \"discord_webhook\": \"\",\r\n  \"telegram_bot_token\": \"\",\r\n  \"telegram_chat_id\": \"\"\r\n}\r\n```\r\n\r\nResolution order for each field: **command-line argument** → **config.json** → empty (skipped). When OpenClaw asks the user for these values, write the answers into `config.json`.\r\n\r\n## Face Database\r\n\r\nThe face database stores registered personnel. Faces in the database are considered \"known\" and will NOT trigger alerts.\r\n\r\n### Directory Structure\r\n\r\n```\r\nface_db/\r\n├── John_Smith/\r\n│   ├── front.jpg\r\n│   ├── side.jpg\r\n│   └── another_angle.png\r\n├── Jane_Doe/\r\n│   └── photo1.jpg\r\n├── Security_Guard_01/\r\n│   ├── img1.jpg\r\n│   └── img2.jpeg\r\n└── face_db.pkl          ← auto-generated cache (do not edit manually)\r\n```\r\nIf the face database is not enabled, everyone will be treated as strangers.\r\n\r\n### Naming Rules\r\n\r\n| Item | Rule | Example |\r\n|------|------|---------|\r\n| Person folder name | Any valid directory name. This becomes the person's identity label. Use underscores or hyphens instead of spaces. | `John_Smith/`, `guard-01/` |\r\n| Image files | Must have extension `.jpg`, `.jpeg`, `.png`, or `.bmp`. Filename itself does not matter. | `photo1.jpg`, `front_view.png` |\r\n| Image content | Each image should contain exactly ONE clearly visible face of that person. | — |\r\n\r\n### Best Practices\r\n\r\n- Use 2-5 photos per person for better accuracy (different angles, lighting)\r\n- Ensure faces are clearly visible and not occluded\r\n- Minimum recommended face size in photos: 112x112 pixels\r\n- Avoid group photos — use single-person portraits\r\n- If the database is empty or missing, ALL detected faces are treated as strangers\r\n\r\n### Building the Cache\r\n\r\nThe main script auto-builds `face_db.pkl` on first run. To pre-build manually:\r\n\r\n```bash\r\n.venv/bin/python build_face_db.py --face_db ./face_db\r\n```\r\n\r\nIf you add/remove photos, delete `face_db.pkl` and re-run to rebuild.\r\n\r\n## Parameters\r\n\r\n### Required\r\n\r\n| Parameter | Description |\r\n|-----------|-------------|\r\n| `--rtsp_url` | Video source. Accepts RTSP URL (e.g., `rtsp://192.168.1.100/live/stream1`) or local file path (e.g., `/path/to/video.mp4`). |\r\n\r\n### Optional\r\n\r\n| Parameter | Default | Type | Description |\r\n|-----------|---------|------|-------------|\r\n| `--det_model` | `models/det_10g.onnx` | path | Path to the SCRFD face detection ONNX model. |\r\n| `--rec_model` | `models/w600k_r50.onnx` | path | Path to the ArcFace face recognition ONNX model. |\r\n| `--face_db` | `./face_db` | path | Path to the registered face database directory. |\r\n| `--db_match_threshold` | `0.4` | float (0-1) | Cosine similarity threshold for database matching. A detected face with similarity >= this value to any registered face is considered \"known\". Increase to reduce false matches (stricter); decrease to be more lenient. |\r\n| `--stranger_match_threshold` | `0.35` | float (0-1) | Cosine similarity threshold for cross-frame stranger tracking. Used to determine if a stranger in the current frame is the same person seen in previous frames. Lower than `db_match_threshold` because appearance varies more across frames. |\r\n| `--loiter_threshold` | `300` | int (seconds) | How long a stranger must remain in view before triggering an alert. Default is 300 seconds (5 minutes). Set lower for more sensitive detection. |\r\n| `--sample_interval` | `2.0` | float (seconds) | How often to run face detection on the video stream. Lower values increase CPU usage but improve tracking accuracy. |\r\n| `--det_thresh` | `0.5` | float (0-1) | Face detection confidence threshold. Faces below this confidence are ignored. Lower to detect more faces (may include false positives); raise to only detect clear faces. |\r\n| `--min_face_size` | `40` | int (pixels) | Minimum face width/height in pixels. Faces smaller than this are skipped. Helps filter out distant or blurry faces. |\r\n| `--output_dir` | `./alerts` | path | Directory where alert face snapshots are saved. Created automatically if it doesn't exist. |\r\n| `--run_time` | `0` | int (seconds) | Maximum run time. `0` means unlimited (runs until stream ends or user interrupt). |\r\n| `--cooldown` | `300` | int (seconds) | Per-stranger alert cooldown. Same stranger won't re-alert within this window. |\r\n| `--fps` | `15` | int | \n\nArchive v2.0.0: 8 files, 27448 bytes\n\nFiles: build_face_db.py (2996b), README.md (19220b), requirements.txt (54b), setup.sh (4143b), skill-card.md (3165b), SKILL.md (8742b), suspicious_person_detector.py (38245b), _meta.json (141b)\n\nArchive v1.0.1: 7 files, 22184 bytes\n\nFiles: build_face_db.py (2996b), README.md (10844b), requirements.txt (44b), setup.sh (3094b), SKILL.md (12716b), suspicious_person_detector.py (31766b), _meta.json (141b)\n\nArchive v1.0.0: 7 files, 22160 bytes\n\nFiles: build_face_db.py (2996b), README.md (10767b), requirements.txt (44b), setup.sh (3094b), SKILL.md (12716b), suspicious_person_detector.py (31766b), _meta.json (141b)","readmeExcerpt":"Skill: kami-suspicious-person Owner: 13681882136 Summary: Detect unregistered faces loitering in sensitive areas. Supports one OR many RTSP cameras concurrently in a single process (shared ONNX models, per-camera tr... Tags: latest:2.0.4 Version history: v2.0.4 | 2026-06-09T05:57:20.221Z | user add alert img v2.0.3 | 2026-05-29T03:10:00.873Z | user add multi cma v2.0.2 | 2026-05-28T02:39:45.908Z | user remove env v2.","codeSnippets":[],"executableExamples":[],"parameters":null,"dependencies":[],"permissions":[],"extractedFiles":[{"path":"SKILL.md","content":"---\r\nname: kami-suspicious-person\r\ndescription: Detect unregistered faces loitering in sensitive areas. Supports one OR many RTSP cameras concurrently in a single process (shared ONNX models, per-camera tracker). Runs continuously, outputs alarm JSON to stdout each time a stranger exceeds the loiter threshold, then keeps monitoring. No local GPU needed for face detection (CPU inference via ONNX).\r\nversion: 2.0.4\r\nauthor: kami-smarthome\r\ntags:\r\n  - smart-home\r\n  - face-recognition\r\n  - stranger-detection\r\n  - loitering-detection\r\n  - surveillance\r\n  - security\r\n  - insightface\r\n  - arcface\r\n  - rtsp\r\n  - edge-ai\r\ntriggers:\r\n  - detect stranger\r\n  - detect unknown person\r\n  - detect unregistered face\r\n  - stranger loitering\r\n  - unknown face detection\r\n  - suspicious person\r\n  - face recognition alert\r\n  - start suspicious person monitoring\r\n  - begin stranger detection\r\nmetadata:\r\n  openclaw:\r\n    requires:\r\n      bins:\r\n        - python3.10\r\n      hardware:\r\n        cpu: \"4+ cores (x86_64 / ARM64)\"\r\n        memory: \"8 GB+\"\r\n        storage: \"10 GB+\"\r\n        gpu: \"optional (speeds up ONNX inference)\"\r\n      network:\r\n        - \"RTSP camera access (LAN)\"\r\n        - \"Internet (KamiClaw API)\"\r\n      devices:\r\n        - \"RTSP IP camera\"\r\n    emoji: \"🕵️\"\r\n---\r\n\r\n# Kami Suspicious Person Detection\r\n\r\nDetect unregistered face loitering events in sensitive areas. The script runs continuously and outputs an alarm JSON line to stdout each time a stranger exceeds the loiter threshold. It does NOT exit after an alarm — it keeps monitoring. Set `run_time: 0` for unlimited operation.\r\n\r\nUses ONNX models directly (no insightface package dependency):\r\n- **SCRFD** (`det_10g.onnx`) — face detection + 5-point landmarks\r\n- **ArcFace** (`w600k_r50.onnx`) — 512-dim face embedding extraction\r\n\r\n## Privacy Policy\r\n\r\nFor privacy policy details, see: <https://kamiclaw-skill.kamihome.com/privacy>\r\n\r\n## How It Works\r\n\r\n1. **Face detection + landmarks** (CPU): SCRFD detects faces every `sample_interval` seconds.\r\n2. **Face alignment + embedding**: ArcFace extracts 512-dim embeddings from aligned 112×112 face crops.\r\n3. **Database matching**: Compare embeddings against the registered face database via cosine similarity. Registered faces are skipped.\r\n4. **Stranger tracking**: Track unregistered faces across frames using sliding-average embedding.\r\n5. **Loiter alarm**: When a stranger stays longer than `loiter_threshold`, output alarm JSON to stdout and save a face snapshot. After `cooldown`, the same stranger can trigger again if still present.\r\n\r\n## When to Use\r\n\r\n- Monitor a camera feed for unregistered/unknown people\r\n- Detect strangers loitering in restricted or sensitive areas\r\n- Get real-time alerts when an unknown face stays too long in view\r\n- Run continuous face recognition surveillance\r\n\r\n## Installation\r\n\r\n```bash\r\nbash setup.sh\r\n```\r\n\r\nNo `sudo` required. The script auto-bootstraps **python3.10** in user space (via [uv](https://github.com/astral-sh/uv) when neede"},{"path":"README.md","content":"# Kami Suspicious Person Detector\r\n\r\nReal-time unregistered face loitering detection for sensitive areas. Uses SCRFD + ArcFace ONNX models directly (no insightface package dependency) for face detection and recognition. Cross-platform: works on Linux, macOS, and Windows with CPU inference. The script runs continuously — each time a stranger loiters beyond the threshold, it outputs an alarm JSON line to stdout and keeps monitoring.\r\n\r\n**Multi-camera capable.** A single process can monitor an arbitrary number of RTSP cameras concurrently (e.g. `living_room` + `office_door` + …). The SCRFD + ArcFace ONNX models AND the registered face database are loaded **once and shared** across all cameras; each camera owns an independent frame grabber, stranger tracker and snapshot directory. Push channels (Feishu / Discord / Telegram) are shared — every camera uses the same webhooks. The face database is a fixed directory `<skill_dir>/face_db/` and is NOT configurable.\r\n\r\n## How It Works\r\n\r\nThe detector monitors an RTSP camera stream (or local video file), detects faces, compares them against a registered face database, and tracks unregistered faces over time. When a stranger remains in view longer than the configured threshold (default: 5 minutes), the script outputs an alarm JSON to stdout, saves a face snapshot, and continues monitoring. A per-stranger cooldown prevents repeated alerts for the same person.\r\n\r\n```\r\nStart script → Monitor stream → Stranger detected → Track duration\r\n                                                        ↓\r\n                              Duration >= threshold → Output alarm JSON → Continue monitoring\r\n```\r\n\r\n## Quick Start\r\n\r\n```bash\r\n# 1. Install dependencies\r\nbash setup.sh\r\n\r\n# 2. (Optional) Add registered faces to the database\r\n#    See \"Face Database\" section below\r\n\r\n# 3. Run detection\r\n.venv/bin/python suspicious_person_detector.py --rtsp_url rtsp://192.168.1.100/live/stream1\r\n```\r\n\r\n## Installation\r\n\r\n```bash\r\nbash setup.sh\r\n```\r\n\r\nNo `sudo` required. The script will:\r\n- Auto-bootstrap **Python 3.10** in user space (via [uv](https://github.com/astral-sh/uv)) — no system-level package manager needed\r\n- Create a `.venv/` virtual environment\r\n- Install all pip dependencies (`onnxruntime`, `opencv-python-headless`, `numpy`)\r\n- Create required directories (`alerts/`, `face_db/`, `models/`)\r\n- Download SCRFD (`det_10g.onnx`, ~16MB) and ArcFace (`w600k_r50.onnx`, ~166MB) models\r\n\r\nWorks on Linux and macOS. No GPU or insightface package needed.\r\n\r\n## Configuration File (config.json)\r\n\r\nA `config.json` file in the skill directory persists user-provided values so you don't have to pass them on every run. Empty fields are ignored. Command-line arguments take priority over `config.json`.\r\n\r\n```json\r\n{\r\n  \"cameras\": [\r\n    {\r\n      \"name\": \"living_room\",\r\n      \"rtsp_url\": \"rtsp://192.168.1.100/live/stream1\"\r\n    },\r\n    {\r\n      \"name\": \"office_door\",\r\n      \"rtsp_url\": \"rtsp://192.168.1.101/live/stream1\"\r\n    }\r\n  ],\r\n  \"loiter_thr"},{"path":"_meta.json","content":"{\n  \"ownerId\": \"kn7e9156e1awf00v1sas2wkdfd85a4zh\",\n  \"slug\": \"kami-suspicious-person\",\n  \"version\": \"2.0.4\",\n  \"publishedAt\": 1780984640221\n}"},{"path":"skill-card.md","content":"## Description:\n\nDetects unregistered faces loitering in sensitive areas across one or many RTSP cameras using shared ONNX face detection and recognition models.\n\nThis skill is ready for commercial/non-commercial use.\n\n## Publisher:\n\n[13681882136](https://clawhub.ai/user/13681882136)\n\n### License/Terms of Use:\n\nMIT-0\n\n## Use Case:\n\nExternal users and operators use this skill to configure and run continuous camera monitoring for unknown-face loitering events in sensitive areas. It helps produce alerts, face snapshot files, and optional push notifications when an unregistered person remains in view beyond the configured threshold.\n\n### Deployment Geography for Use:\n\nGlobal\n\n## Known Risks and Mitigations:\n\nRisk: Biometric snapshots and camera feed details may expose sensitive personal information.\n\nMitigation: Review privacy requirements before use, restrict access to alert images and logs, and retain snapshots only for an approved operational purpose.\n\nRisk: Configuration values and logs may contain camera URLs, bot tokens, webhook URLs, or other secrets.\n\nMitigation: Protect config.json and runtime logs as secrets, use dedicated low-privilege camera and bot credentials, and rotate credentials if exposure is suspected.\n\nRisk: Installer, dependency, and model downloads introduce supply-chain risk.\n\nMitigation: Prefer pinned and verified installers, Python dependencies, and model files before production deployment.\n\nRisk: Feishu image fallback can upload face snapshots to a public image host.\n\nMitigation: Avoid enabling Feishu unless the public image-host fallback is removed or explicitly disabled, or use approved inline image upload credentials only.\n\nRisk: Background monitoring can continue collecting alerts beyond the operator's immediate attention.\n\nMitigation: Run the detector under an accountable operator, document the monitoring scope, and periodically confirm that the process, cameras, and alert destinations remain intended.\n\n## Reference(s):\n\n- [ClawHub Skill Page](https://clawhub.ai/13681882136/skills/kami-suspicious-person)\n- [ClawHub Publisher Profile](https://clawhub.ai/user/13681882136)\n- [Model Archive](https://publicfiles.xiaoyi.com/kami-suspicious-person-model.zip)\n- [uv Project](https://github.com/astral-sh/uv)\n- [Feishu Open Platform](https://open.feishu.cn/)\n- [Discord Developer Portal](https://discord.com/developers/applications)\n\n## Skill Output:\n\n**Output Type(s):** [text, shell commands, configuration, guidance, JSON, files]\n\n**Output Format:** [Markdown guidance with shell commands, configuration updates, JSON alert records, and saved face snapshot files]\n\n**Output Parameters:** [1D]\n\n**Other Properties Related to Output:** [Runs continuously when launched; alerts are written to stdout and an inbox JSONL file, with optional Feishu, Discord, or Telegram push delivery.]\n\n## Skill Version(s):\n\n2.0.4 (source: frontmatter and server release evidence)\n\n## Ethical Considerations:\n\nUsers should evaluate whether this skill is appropr"},{"path":"config.json","content":"{\r\n  \"cameras\": [\r\n    {\r\n      \"name\": \"\",\r\n      \"rtsp_url\": \"\"\r\n    }\r\n  ],\r\n  \"loiter_threshold\": \"\",\r\n  \"feishu_webhook\": \"\",\r\n  \"feishu_secret\": \"\",\r\n  \"feishu_app_id\": \"\",\r\n  \"feishu_app_secret\": \"\",\r\n  \"discord_webhook\": \"\",\r\n  \"telegram_bot_token\": \"\",\r\n  \"telegram_chat_id\": \"\"\r\n}"}],"languages":[],"docsSourceLabel":"CLAWHUB","editorialOverview":"Detect unregistered faces loitering in sensitive areas. Supports one OR many RTSP cameras concurrently in a single process (shared ONNX models, per-camera tr... Skill: kami-suspicious-person Owner: 13681882136 Summary: Detect unregistered faces loitering in sensitive areas. Supports one OR many RTSP cameras concurrently in a single process (shared ONNX models, per-camera tr... Tags: latest:2.0.4 Version history: v2.0.4 | 2026-06-09T05:57:20.221Z | user add alert img v2.0.3 | 2026-05-29T03:10:00.873Z | user add multi cma v2.0.2 | 2026-05-28T02:39:45.908Z | user remove env v2.","editorialQuality":{"score":100,"threshold":65,"status":"ready","wordCount":1612,"uniquenessScore":46,"reasons":[]}},"media":{"evidence":{"source":"no-media","verified":false,"confidence":"low","updatedAt":"2026-10-11T11:48:12.421Z","emptyReason":"No screenshots, media assets, or demo links are available."},"primaryImageUrl":null,"mediaAssetCount":0,"assets":[],"demoUrl":null},"ownerResources":{"evidence":{"source":"unclaimed","verified":false,"confidence":"low","updatedAt":"2026-10-11T11:48:12.421Z","emptyReason":"This page has not been claimed by the agent owner."},"hasCustomPage":false,"customPageUpdatedAt":null,"customLinks":[],"structuredLinks":{"docsUrl":null,"demoUrl":null,"supportUrl":null,"pricingUrl":null,"statusUrl":null},"customPage":null},"relatedAgents":{"evidence":{"source":"protocol-neighbors","verified":false,"confidence":"medium","updatedAt":"2026-10-11T14:17:50.189Z","emptyReason":null},"items":[{"id":"8ebccd8e-3863-4187-8355-c3f14e1f9edf","entityType":"agent","canonicalPath":"/agent/iofficeai-aionui","slug":"iofficeai-aionui","name":"AionUi","description":"Free, local, open-source 24/7 Cowork app and OpenClaw for Gemini CLI, Claude Code, Codex, OpenCode, Qwen Code, Goose CLI, Auggie, and more | 🌟 Star if you like it!","url":"https://github.com/iOfficeAI/AionUi","homepage":"https://www.aionui.com","source":"GITHUB_REPOS","protocols":["MCP","OPENCLAW"],"capabilities":[],"safetyScore":100,"overallRank":70,"updatedAt":"2026-10-09T19:11:12.944Z","createdAt":"2026-02-25T03:38:16.584Z","downloads":null},{"id":"b917f68a-ebff-438e-84f8-3f4b2494c0bc","entityType":"agent","canonicalPath":"/agent/activepieces-activepieces","slug":"activepieces-activepieces","name":"activepieces","description":"AI Agents & MCPs & AI Workflow Automation • (~400 MCP servers for AI agents) • AI Automation / AI Agent with MCPs • AI Workflows & AI Agents • MCPs for AI Agents","url":"https://github.com/activepieces/activepieces","homepage":"https://www.activepieces.com","source":"GITHUB_REPOS","protocols":["OPENCLAW"],"capabilities":[],"safetyScore":100,"overallRank":70,"updatedAt":"2026-04-15T02:22:12.426Z","createdAt":"2026-02-25T03:38:12.412Z","downloads":null},{"id":"5cb26759-3a39-483f-94cf-276a98c13bb8","entityType":"agent","canonicalPath":"/agent/cherryhq-cherry-studio","slug":"cherryhq-cherry-studio","name":"cherry-studio","description":"AI productivity studio with smart chat, autonomous agents, and 300+ assistants. Unified access to frontier LLMs","url":"https://github.com/CherryHQ/cherry-studio","homepage":"https://cherry-ai.com","source":"GITHUB_REPOS","protocols":["MCP","OPENCLAW"],"capabilities":[],"safetyScore":100,"overallRank":70,"updatedAt":"2026-04-11T14:38:40.986Z","createdAt":"2026-02-25T03:38:19.379Z","downloads":null},{"id":"6f6582d0-5d76-4f0f-b81d-86520247950b","entityType":"agent","canonicalPath":"/agent/copilotkit-copilotkit","slug":"copilotkit-copilotkit","name":"CopilotKit","description":"The Frontend for Agents & Generative UI. React + Angular","url":"https://github.com/CopilotKit/CopilotKit","homepage":"https://docs.copilotkit.ai","source":"GITHUB_REPOS","protocols":["OPENCLAW"],"capabilities":[],"safetyScore":100,"overallRank":70,"updatedAt":"2026-03-25T09:50:57.846Z","createdAt":"2026-02-25T03:39:14.617Z","downloads":null}],"links":{"hub":"/agent","source":"/agent/source/clawhub","protocols":[{"label":"OpenClaw","href":"/agent/protocol/openclew"}]}}}