Skill: 43622283/li-sentry-check (version 0.4.0)

--- name: li_sentry_check description: "Multi-platform server inspection and health check skill. SSH into remote Linux servers using key-based authentication, run read-only inspection commands (CPU, memory, disk, network, services, security), and generate structured Markdown reports with anomaly highlighting. Use when the user asks to inspect servers, run health checks, check system metrics, perform 巡检/巡查, gather system status, or generate inspection reports. Compatible with nanobot, OpenClaw, and Hermes agent." --- # li_sentry_check Multi-platform server inspection and health check via SSH. ## Security Declaration **This skill is strictly read-only and does NOT:** - ❌ Modify any server configuration - ❌ Install or remove software - ❌ Restart or stop services - ❌ Write to any file on the remote server - ❌ Exfiltrate data to external services - ❌ Access local files other than: `references/targets.yaml`, `references/checks.yaml`, and the SSH private key specified in `keyPath` - ❌ Make any network connections other than SSH to the target server specified in `targets.yaml` - ❌ Execute arbitrary commands — only commands from `references/checks.yaml` are allowed **This skill ONLY:** - ✅ Reads system information via predefined read-only commands - ✅ Generates a local Markdown/JSON report - ✅ Connects to ONE remote server via SSH using the key specified in `targets.yaml` ## Overview Read-only inspection of remote Linux hosts over SSH using a dedicated key. Collects system metrics, service status, security events, and generates a structured Markdown report with anomaly highlighting. ## Platform Support | Platform | Script | Runtime | |-----------|-----------------|------------| | OpenClaw | `scripts/inspect.mjs` | Node.js 24+ | | NanoBot | `scripts/inspect.py` | Python 3.10+ | | Hermes | `scripts/inspect.py` | Python 3.10+ | ## Safety (Default Deny) - **Only** run commands defined in `references/checks.yaml` - **No** state-changing commands (no installs, no config edits, no restarts) - **Only** SSH key authentication (no passwords) - **BatchMode=yes** — non-interactive SSH only ## Config - **Targets**: `references/targets.yaml` - **Allowed checks**: `references/checks.yaml` ## How To Run ### NanoBot / Hermes (Python) ```bash python3 scripts/inspect.py --target bogon --checks daily ``` ### OpenClaw (Node.js) ```bash node scripts/inspect.mjs --target bogon --checks daily ``` ### Options | Option | Description | Default | |------------|------------------------------------------|---------| | `--target` | Target name from `targets.yaml` | (required) | | `--checks` | Check group: `basic`, `services`, `daily`| `basic` | | `--format` | Output format: `markdown`, `json` | `markdown` | | `--output` | Write report to file instead of stdout | stdout | ## Check Groups | Group | Description | |------------|------------------------------------------| | `basic` | Hardware resources: CPU, memory, disk, network | | `services` | Service status and error logs (from targets.yaml) | | `daily` | Full inspection: basic + services + security + logs | ## Extending 1. **Add target**: Edit `references/targets.yaml` 2. **Add checks**: Edit `references/checks.yaml` 3. **Add check group**: Define new group in `checks.yaml` ## SSH Key Setup ```bash # Generate key pair ssh-keygen -t rsa -b 4096 -f ~/.ssh/li_sentry_check -N "" # Copy to remote server ssh-copy-id -i ~/.ssh/li_sentry_check.pub inspector@<SERVER_IP> # Test connection ssh -i ~/.ssh/li_sentry_check inspector@<SERVER_IP> ``` ## Security Best Practices - **Key permissions**: `chmod 600 ~/.ssh/li_sentry_check` - **Host verification**: For production, pre-populate `known_hosts` instead of `accept-new` - **Service names**: Only alphanumeric, hyphens, underscores allowed (validated before use) - **Command allowlist**: Never modify `checks.yaml` with state-changing commands - **Report handling**: Reports may contain system data — do not share publicly ## Report Output Reports are generated in Markdown format with: - **Summary section**: Overall health status, anomaly count - **Anomaly section**: ⚠️ Highlighted issues requiring attention - **Normal section**: Collapsible normal check results - **Details**: Full command output for each check ## Architecture ``` li_sentry_check/ ├── SKILL.md # This file ├── _meta.json # Skill metadata ├── references/ │ ├── targets.yaml # Target server configuration │ └── checks.yaml # Command allowlist └── scripts/ ├── inspect.mjs # Node.js implementation (OpenClaw) └── inspect.py # Python implementation (NanoBot/Hermes) ```

path: README.de.md
content: # 🔍 li_sentry_check - Server-Inspektions-Skill

> Plattformübergreifende Server-Inspektions- und Gesundheits-Check-Skill. SSH-Anmeldung an entfernten Linux-Servern mit Schlüsselauthentifizierung, Ausführung von schreibgeschützten Inspektionsbefehlen und Generierung strukturierter Markdown-Berichte.

[![Version](https://img.shields.io/badge/version-0.1.0-blue.svg)](https://clawhub.ai/skills/li_sentry_check)
[![Plattformen](https://img.shields.io/badge/plattformen-nanobot%20%7C%20OpenClaw%20%7C%20Hermes-green.svg)]()
[![Lizenz](https://img.shields.io/badge/lizenz-MIT-green.svg)](LICENSE)

## 📋 Übersicht

`li_sentry_check` ist eine plattformübergreifende Server-Inspektions-Skill, die **nanobot**, **OpenClaw** und **Hermes Agent** unterstützt. Sie meldet sich über SSH-Schlüsselauthentifizierung bei entfernten Linux-Servern an, führt schreibgeschützte Inspektionsbefehle aus (CPU, Speicher, Festplatte, Netzwerk, Dienste, Sicherheit) und generiert strukturierte Markdown-Berichte mit automatischer Hervorhebung von Anomalien.

## ✨ Kernfunktionen

| Funktion | Beschreibung |
|----------|--------------|
| 🔐 SSH-Schlüsselauthentifizierung | Nur Schlüsselauthentifizierung, Passwort-Anmeldung deaktiviert, Sicherheit gehärtet |
| 📊 Hardware-Inspektion | CPU, Speicher, Festplatte, Netzwerknutzung |
| 🖥️ Dienst-Inspektion | Wichtiger Dienststatus, Fehlerprotokolle |
| 🛡️ Sicherheitsinspektion | Anomale SSH-Anmeldungen, Firewall-Warnungen, Kernel-Fehler |
| 📝 Strukturierte Berichte | Markdown/JSON-Format, Anomalien priorisiert |
| 🌐 Plattformübergreifend | Unterstützt nanobot, OpenClaw, Hermes |

## 🚀 Schnellstart

### 1. Skill Installieren

```bash
# nanobot
./manage.sh skill install li_sentry_check

# OpenClaw
npx clawhub@latest install li_sentry_check

# Hermes
hermes skill install li_sentry_check
```

### 2. SSH-Schlüssel Konfigurieren

```bash
# Schlüsselpaar generieren
ssh-keygen -t rsa -b 4096 -f ~/.ssh/li_sentry_check -N ""

# Öffentlichen Schlüssel auf den entfernten Server kopieren
ssh-copy-id -i ~/.ssh/li_sentry_check.pub inspector@<SERVER_IP>

# Verbindung testen
ssh -i ~/.ssh/li_sentry_check inspector@<SERVER_IP>
```

### 3. ZielsERVER Konfigurieren

`references/targets.yaml` bearbeiten:

```yaml
targets:
  produktions-web:
    host: IHRE_SERVER_IP
    port: 22
    user: inspector
    keyPath: ~/.ssh/li_sentry_check
    services:
      - nginx
      - docker
      - sshd
```

### 4. Inspektion Ausführen

```bash
# Basisinspektion (Hardware-Ressourcen)
python3 scripts/inspect.py --target produktions-web --checks basic

# Dienst-Inspektion
python3 scripts/inspect.py --target produktions-web --checks services

# Vollständige Inspektion (Basis + Dienste + Sicherheit + Protokolle)
python3 scripts/inspect.py --target produktions-web --checks daily

# JSON-Format-Ausgabe
python3 scripts/inspect.py --target produktions-web --checks daily --format json

# In Datei ausgeben
python3 scripts/inspect.py --target produktions-web --checks daily --output bericht.md
```

## 📖 Inspektions-Check-Gruppen

| Gruppe | Inhalt | Befehle |
|--------|--------|---------|
| `basic` | CPU, Speicher, Festplatte, Netzwerk | 8 |
| `services` | Dienststatus + Fehlerprotokolle (dynamisch) | 3×N |
| `daily` | Vollständige Inspektion (Basis + Dienste + Sicherheit + Protokolle) | 26 |

## 📊 Bericht-Beispiel

```markdown
# 🔍 Server-Inspektionsbericht

- Ziel: produktions-web
- Host: IHRE_SERVER_IP
- Benutzer: inspector
- Checks: daily
- Gestartet: 2026-04-26T09:00:00+00:00
- Gesamtchecks: 26
- ⚠️ Anomalien: 3

## Gesamtstatus: ⚠️ WARNUNG

## ⚠️ Anomalien (Priorität)

### ⚠️ systemd_failed_units
Befehl: `systemctl --failed --no-pager`
Status: OK (enthält Anomalien)

Ausgabe:
```
UNIT          LOAD   ACTIVE SUB    DESCRIPTION
mcelog.service loaded failed failed Machine Check Exception Logging Daemon
```
```

## 🔧 Befehlszeilen-Optionen

| Option | Beschreibung | Standard |
|--------|--------------|----------|
| `--target` | Zielserver-Name (in targets.yaml definiert) | (erforderlich) |
| `--checks` | Check-Gruppe: `basic`, `services`, `daily` | `basic` |
| `--format` | Ausgabeformat: `markdown`, `json` | `markdown` |
| `--output` | In Datei ausgeben (Standard: stdout) | stdout |

## 🌐 Plattformübergreifende Unterstützung

| Plattform | Laufzeit | Script | Befehl |
|-----------|----------|--------|--------|
| **OpenClaw** | Node.js 24+ | `scripts/inspect.mjs` | `node scripts/inspect.mjs --target bogon --checks daily` |
| **NanoBot** | Python 3.10+ | `scripts/inspect.py` | `python3 scripts/inspect.py --target bogon --checks daily` |
| **Hermes** | Python 3.10+ | `scripts/inspect.py` | `python3 scripts/inspect.py --target bogon --checks daily` |

## 📁 Dateistruktur

```
li_sentry_check/
├── SKILL.md                  # Skill-Dokumentation
├── _meta.json                # Skill-Metadaten
├── design.md                 # Design-Dokumentation
├── references/
│   ├── targets.yaml          # ZielsERVER-Konfiguration
│   └── checks.yaml           # Inspektionsbefehls-Whitelist
└── scripts/
    ├── inspect.mjs           # Node.js-Implementierung (OpenClaw)
    └── inspect.py            # Python-Implementierung (NanoBot/Hermes)
```

## 🔒 Sicherheits-Best Practices

- **Schlüsselberechtigungen**: `chmod 600 ~/.ssh/li_sentry_check`
- **Host-Verifizierung**: Für die Produktion `known_hosts` vorab befüllen statt `accept-new` zu verwenden
- **Dienstnamen**: Nur alphanumerisch, Bindestriche, Unterstriche erlaubt (vor Verwendung validiert)
- **Befehls-Whitelist**: `checks.yaml` niemals mit zustandsändernden Befehlen modifizieren
- **Berichts-Handhabung**: Berichte können Systemdaten enthalten — nicht öffentlich teilen

## 🔧 Erweiterungsleitfaden

### Neuen Zielserver Hinzufügen

`references/targets.yaml` bearbeiten:

```yaml
targets:
  datenbank-server:
    host: IHRE_SERVER_IP
    port: 22
    user: inspector
    keyPath: ~/.ssh/li_sentry_check
    services:
      - mysql
      - redis
```

### Neue Check-Gruppe Hinzufügen

`references/checks.yaml` bearbeiten:

```yaml
checks:
  datenbank:
    description: Datenbank-Inspektion
    commands:
      - id: mysql_status
        cmd: "systemctl status mysql --no-pager | sed -n '1,20p'"
        timeoutSec: 10
      - id: mysql_connections
        cmd: "mysql -e 'SHOW STATUS LIKE \"Threads_connected\"' || true"
        timeoutSec: 15
```

## 📝 Versionsverlauf

| Version | Datum | Änderungen |
|---------|-------|------------|
| 0.1.0 | 2026-04-26 | Erstveröffentlichung: Basis-, Dienst- und Vollinspektion |

## 📄 Lizenz

MIT-Lizenz


path: README.en.md
content: # 🔍 li_sentry_check - Server Inspection Skill

> Multi-platform server inspection and health check skill. SSH into remote Linux servers using key-based authentication, run read-only inspection commands, and generate structured Markdown reports.

[![Version](https://img.shields.io/badge/version-0.1.0-blue.svg)](https://clawhub.ai/skills/li_sentry_check)
[![Platforms](https://img.shields.io/badge/platforms-nanobot%20%7C%20OpenClaw%20%7C%20Hermes-green.svg)]()
[![License](https://img.shields.io/badge/license-MIT-green.svg)](LICENSE)

## 📋 Overview

`li_sentry_check` is a cross-platform server inspection skill supporting **nanobot**, **OpenClaw**, and **Hermes agent**. It logs into remote Linux servers via SSH key authentication, executes read-only inspection commands (CPU, memory, disk, network, services, security), and generates structured Markdown reports with automatic anomaly highlighting.

## ✨ Core Features

| Feature | Description |
|---------|-------------|
| 🔐 SSH Key Authentication | Key-only authentication, password login disabled, security hardened |
| 📊 Hardware Inspection | CPU, memory, disk, network usage |
| 🖥️ Service Inspection | Key service status, error logs |
| 🛡️ Security Inspection | SSH anomalous logins, firewall alerts, kernel errors |
| 📝 Structured Reports | Markdown/JSON format, anomalies prioritized |
| 🌐 Cross-Platform | Supports nanobot, OpenClaw, Hermes |

## 🚀 Quick Start

### 1. Install the Skill

```bash
# nanobot
./manage.sh skill install li_sentry_check

# OpenClaw
npx clawhub@latest install li_sentry_check

# Hermes
hermes skill install li_sentry_check
```

### 2. Configure SSH Keys

```bash
# Generate key pair
ssh-keygen -t rsa -b 4096 -f ~/.ssh/li_sentry_check -N ""

# Copy public key to remote server
ssh-copy-id -i ~/.ssh/li_sentry_check.pub inspector@<SERVER_IP>

# Test connection
ssh -i ~/.ssh/li_sentry_check inspector@<SERVER_IP>
```

### 3. Configure Target Servers

Edit `references/targets.yaml`:

```yaml
targets:
  production-web:
    host: YOUR_SERVER_IP
    port: 22
    user: inspector
    keyPath: ~/.ssh/li_sentry_check
    services:
      - nginx
      - docker
      - sshd
```

### 4. Run Inspection

```bash
# Basic inspection (hardware resources)
python3 scripts/inspect.py --target production-web --checks basic

# Service inspection
python3 scripts/inspect.py --target production-web --checks services

# Full inspection (basic + services + security + logs)
python3 scripts/inspect.py --target production-web --checks daily

# JSON format output
python3 scripts/inspect.py --target production-web --checks daily --format json

# Output to file
python3 scripts/inspect.py --target production-web --checks daily --output report.md
```

## 📖 Inspection Check Groups

| Group | Content | Commands |
|-------|---------|----------|
| `basic` | CPU, memory, disk, network | 8 |
| `services` | Service status + error logs (dynamic) | 3×N |
| `daily` | Full inspection (basic + services + security + logs) | 26 |

## 📊 Report Example

```markdown
# 🔍 Server Inspection Report

- Target: production-web
- Host: YOUR_SERVER_IP
- User: inspector
- Checks: daily
- Started: 2026-04-26T09:00:00+00:00
- Total checks: 26
- ⚠️ Anomalies: 3

## Overall Status: ⚠️ WARNING

## ⚠️ Anomalies (Priority)

### ⚠️ systemd_failed_units
Command: `systemctl --failed --no-pager`
Status: OK (contains anomalies)

Output:
```
UNIT          LOAD   ACTIVE SUB    DESCRIPTION
mcelog.service loaded failed failed Machine Check Exception Logging Daemon
```
```

## 🔧 Command Line Options

| Option | Description | Default |
|--------|-------------|---------|
| `--target` | Target server name (defined in targets.yaml) | (required) |
| `--checks` | Check group: `basic`, `services`, `daily` | `basic` |
| `--format` | Output format: `markdown`, `json` | `markdown` |
| `--output` | Output to file (default: stdout) | stdout |

## 🌐 Cross-Platform Support

| Platform | Runtime | Script | Command |
|----------|---------|--------|---------|
| **OpenClaw** | Node.js 24+ | `scripts/inspect.mjs` | `node scripts/inspect.mjs --target bogon --checks daily` |
| **NanoBot** | Python 3.10+ | `scripts/inspect.py` | `python3 scripts/inspect.py --target bogon --checks daily` |
| **Hermes** | Python 3.10+ | `scripts/inspect.py` | `python3 scripts/inspect.py --target bogon --checks daily` |

## 📁 File Structure

```
li_sentry_check/
├── SKILL.md                  # Skill documentation
├── _meta.json                # Skill metadata
├── design.md                 # Design documentation
├── references/
│   ├── targets.yaml          # Target server configuration
│   └── checks.yaml           # Inspection command allowlist
└── scripts/
    ├── inspect.mjs           # Node.js implementation (OpenClaw)
    └── inspect.py            # Python implementation (NanoBot/Hermes)
```

## 🔒 Security Best Practices

- **Key permissions**: `chmod 600 ~/.ssh/li_sentry_check`
- **Host verification**: For production, pre-populate `known_hosts` instead of `accept-new`
- **Service names**: Only alphanumeric, hyphens, underscores allowed (validated before use)
- **Command allowlist**: Never modify `checks.yaml` with state-changing commands
- **Report handling**: Reports may contain system data — do not share publicly

## 🔧 Extension Guide

### Add a New Target Server

Edit `references/targets.yaml`:

```yaml
targets:
  database-server:
    host: YOUR_SERVER_IP
    port: 22
    user: inspector
    keyPath: ~/.ssh/li_sentry_check
    services:
      - mysql
      - redis
```

### Add a New Check Group

Edit `references/checks.yaml`:

```yaml
checks:
  database:
    description: Database inspection
    commands:
      - id: mysql_status
        cmd: "systemctl status mysql --no-pager | sed -n '1,20p'"
        timeoutSec: 10
      - id: mysql_connections
        cmd: "mysql -e 'SHOW STATUS LIKE \"Threads_connected\"' || true"
        timeoutSec: 15
```

## 📝 Version History

| Version | Date | Changes |
|---------|------|---------|
| 0.1.0 | 2026-04-26 | Initial release: basic, services, and full inspection |

## 📄 License

MIT License


path: README.es.md
content: # 🔍 li_sentry_check - Habilidad de Inspección de Servidores

> Habilidad multiplataforma de inspección y verificación de salud de servidores. Acceso SSH a servidores Linux remotos mediante autenticación por clave, ejecución de comandos de inspección de solo lectura y generación de informes estructurados en Markdown.

[![Versión](https://img.shields.io/badge/versión-0.1.0-blue.svg)](https://clawhub.ai/skills/li_sentry_check)
[![Plataformas](https://img.shields.io/badge/plataformas-nanobot%20%7C%20OpenClaw%20%7C%20Hermes-green.svg)]()
[![Licencia](https://img.shields.io/badge/licencia-MIT-green.svg)](LICENSE)

## 📋 Resumen

`li_sentry_check` es una habilidad de inspección de servidores multiplataforma que soporta **nanobot**, **OpenClaw** y **Hermes agent**. Se conecta a servidores Linux remotos mediante autenticación por clave SSH, ejecuta comandos de inspección de solo lectura (CPU, memoria, disco, red, servicios, seguridad) y genera informes Markdown estructurados con resaltado automático de anomalías.

## ✨ Funcionalidades Principales

| Funcionalidad | Descripción |
|---------------|-------------|
| 🔐 Autenticación por Clave SSH | Solo autenticación por clave, acceso con contraseña deshabilitado, seguridad reforzada |
| 📊 Inspección de Hardware | CPU, memoria, disco, uso de red |
| 🖥️ Inspección de Servicios | Estado de servicios clave, registros de errores |
| 🛡️ Inspección de Seguridad | Inicios de sesión SSH anómalos, alertas de firewall, errores del kernel |
| 📝 Informes Estructurados | Formato Markdown/JSON, anomalías prioritarias |
| 🌐 Multiplataforma | Soporta nanobot, OpenClaw, Hermes |

## 🚀 Inicio Rápido

### 1. Instalar la Habilidad

```bash
# nanobot
./manage.sh skill install li_sentry_check

# OpenClaw
npx clawhub@latest install li_sentry_check

# Hermes
hermes skill install li_sentry_check
```

### 2. Configurar Claves SSH

```bash
# Generar par de claves
ssh-keygen -t rsa -b 4096 -f ~/.ssh/li_sentry_check -N ""

# Copiar clave pública al servidor remoto
ssh-copy-id -i ~/.ssh/li_sentry_check.pub inspector@<IP_SERVIDOR>

# Probar conexión
ssh -i ~/.ssh/li_sentry_check inspector@<IP_SERVIDOR>
```

### 3. Configurar Servidores Objetivo

Editar `references/targets.yaml`:

```yaml
targets:
  producción-web:
    host: TU_IP_SERVIDOR
    port: 22
    user: inspector
    keyPath: ~/.ssh/li_sentry_check
    services:
      - nginx
      - docker
      - sshd
```

### 4. Ejecutar Inspección

```bash
# Inspección básica (recursos de hardware)
python3 scripts/inspect.py --target producción-web --checks basic

# Inspección de servicios
python3 scripts/inspect.py --target producción-web --checks services

# Inspección completa (básica + servicios + seguridad + registros)
python3 scripts/inspect.py --target producción-web --checks daily

# Salida en formato JSON
python3 scripts/inspect.py --target producción-web --checks daily --format json

# Salida a archivo
python3 scripts/inspect.py --target producción-web --checks daily --output informe.md
```

## 📖 Grupos de Verificación de Inspección

| Grupo | Contenido | Comandos |
|-------|-----------|----------|
| `basic` | CPU, memoria, disco, red | 8 |
| `services` | Estado de servicios + registros de errores (dinámico) | 3×N |
| `daily` | Inspección completa (básica + servicios + seguridad + registros) | 26 |

## 📊 Ejemplo de Informe

```markdown
# 🔍 Informe de Inspección de Servidor

- Objetivo: producción-web
- Host: TU_IP_SERVIDOR
- Usuario: inspector
- Verificaciones: daily
- Iniciado: 2026-04-26T09:00:00+00:00
- Total verificaciones: 26
- ⚠️ Anomalías: 3

## Estado General: ⚠️ ADVERTENCIA

## ⚠️ Anomalías (Prioridad)

### ⚠️ systemd_failed_units
Comando: `systemctl --failed --no-pager`
Estado: OK (contiene anomalías)

Salida:
```
UNIT          LOAD   ACTIVE SUB    DESCRIPTION
mcelog.service loaded failed failed Machine Check Exception Logging Daemon
```
```

## 🔧 Opciones de Línea de Comandos

| Opción | Descripción | Predeterminado |
|--------|-------------|----------------|
| `--target` | Nombre del servidor objetivo (definido en targets.yaml) | (obligatorio) |
| `--checks` | Grupo de verificación: `basic`, `services`, `daily` | `basic` |
| `--format` | Formato de salida: `markdown`, `json` | `markdown` |
| `--output` | Salida a archivo (predeterminado: stdout) | stdout |

## 🌐 Soporte Multiplataforma

| Plataforma | Entorno | Script | Comando |
|------------|---------|--------|---------|
| **OpenClaw** | Node.js 24+ | `scripts/inspect.mjs` | `node scripts/inspect.mjs --target bogon --checks daily` |
| **NanoBot** | Python 3.10+ | `scripts/inspect.py` | `python3 scripts/inspect.py --target bogon --checks daily` |
| **Hermes** | Python 3.10+ | `scripts/inspect.py` | `python3 scripts/inspect.py --target bogon --checks daily` |

## 📁 Estructura de Archivos

```
li_sentry_check/
├── SKILL.md                  # Documentación de la habilidad
├── _meta.json                # Metadatos de la habilidad
├── design.md                 # Documentación de diseño
├── references/
│   ├── targets.yaml          # Configuración de servidores objetivo
│   └── checks.yaml           # Lista blanca de comandos de inspección
└── scripts/
    ├── inspect.mjs           # Implementación Node.js (OpenClaw)
    └── inspect.py            # Implementación Python (NanoBot/Hermes)
```

## 🔒 Mejores Prácticas de Seguridad

- **Permisos de clave**: `chmod 600 ~/.ssh/li_sentry_check`
- **Verificación de host**: Para producción, pre-rellenar `known_hosts` en lugar de usar `accept-new`
- **Nombres de servicios**: Solo alfanumérico, guiones, guiones bajos permitidos (validados antes del uso)
- **Lista blanca de comandos**: Nunca modificar `checks.yaml` con comandos que cambien el estado
- **Manejo de informes**: Los informes pueden contener datos del sistema — no compartir públicamente

## 🔧 Guía de Extensión

### Agregar un Nuevo Servidor Objetivo

Editar `references/targets.yaml`:

```yaml
targets:
  servidor-base-datos:
    host: TU_IP_SERVIDOR
    port: 22
    user: inspector
    keyPath: ~/.ssh/li_sentry_check
    services:
      - mysql
      - redis
```

### Agregar un Nuevo Grupo de Verificación

Editar `references/checks.yaml`:

```yaml
checks:
  base-datos:
    description: Inspección de base de datos
    commands:
      - id: mysql_status
        cmd: "systemctl status mysql --no-pager | sed -n '1,20p'"
        timeoutSec: 10
      - id: mysql_connections
        cmd: "mysql -e 'SHOW STATUS LIKE \"Threads_connected\"' || true"
        timeoutSec: 15
```

## 📝 Historial de Versiones

| Versión | Fecha | Cambios |
|---------|-------|---------|
| 0.1.0 | 2026-04-26 | Versión inicial: inspección básica, de servicios y completa |

## 📄 Licencia

Licencia MIT


path: README.fr.md
content: # 🔍 li_sentry_check - Compétence d'Inspection de Serveurs

> Compétence multi-plateforme d'inspection et de santé des serveurs. Connexion SSH par authentification par clé aux serveurs Linux distants, exécution de commandes d'inspection en lecture seule, et génération de rapports structurés en Markdown.

[![Version](https://img.shields.io/badge/version-0.1.0-blue.svg)](https://clawhub.ai/skills/li_sentry_check)
[![Plateformes](https://img.shields.io/badge/plateformes-nanobot%20%7C%20OpenClaw%20%7C%20Hermes-green.svg)]()
[![Licence](https://img.shields.io/badge/licence-MIT-green.svg)](LICENSE)

## 📋 Aperçu

`li_sentry_check` est une compétence d'inspection de serveurs multi-plateforme qui prend en charge **nanobot**, **OpenClaw** et **Hermes agent**. Il se connecte aux serveurs Linux distants via l'authentification par clé SSH, exécute des commandes d'inspection en lecture seule (CPU, mémoire, disque, réseau, services, sécurité) et génère des rapports Markdown structurés avec mise en surbrillance automatique des anomalies.

## ✨ Fonctionnalités Principales

| Fonctionnalité | Description |
|----------------|-------------|
| 🔐 Authentification par Clé SSH | Authentification par clé uniquement, connexion par mot de passe désactivée, sécurité renforcée |
| 📊 Inspection Matérielle | CPU, mémoire, disque, utilisation du réseau |
| 🖥️ Inspection des Services | État des services clés, journaux d'erreurs |
| 🛡️ Inspection de Sécurité | Connexions SSH anormales, alertes pare-feu, erreurs noyau |
| 📝 Rapports Structurés | Format Markdown/JSON, anomalies prioritaires |
| 🌐 Multi-Plateforme | Prend en charge nanobot, OpenClaw, Hermes |

## 🚀 Démarrage Rapide

### 1. Installer la Compétence

```bash
# nanobot
./manage.sh skill install li_sentry_check

# OpenClaw
npx clawhub@latest install li_sentry_check

# Hermes
hermes skill install li_sentry_check
```

### 2. Configurer les Clés SSH

```bash
# Générer une paire de clés
ssh-keygen -t rsa -b 4096 -f ~/.ssh/li_sentry_check -N ""

# Copier la clé publique sur le serveur distant
ssh-copy-id -i ~/.ssh/li_sentry_check.pub inspector@<IP_SERVEUR>

# Tester la connexion
ssh -i ~/.ssh/li_sentry_check inspector@<IP_SERVEUR>
```

### 3. Configurer les Serveurs Cibles

Modifier `references/targets.yaml` :

```yaml
targets:
  production-web:
    host: IP_DE_VOTRE_SERVEUR
    port: 22
    user: inspector
    keyPath: ~/.ssh/li_sentry_check
    services:
      - nginx
      - docker
      - sshd
```

### 4. Exécuter l'Inspection

```bash
# Inspection de base (ressources matérielles)
python3 scripts/inspect.py --target production-web --checks basic

# Inspection des services
python3 scripts/inspect.py --target production-web --checks services

# Inspection complète (base + services + sécurité + journaux)
python3 scripts/inspect.py --target production-web --checks daily

# Sortie au format JSON
python3 scripts/inspect.py --target production-web --checks daily --format json

# Sortie dans un fichier
python3 scripts/inspect.py --target production-web --checks daily --output rapport.md
```

## 📖 Groupes de Vérification d'Inspection

| Groupe | Contenu | Commandes |
|--------|---------|-----------|
| `basic` | CPU, mémoire, disque, réseau | 8 |
| `services` | État des services + journaux d'erreurs (dynamique) | 3×N |
| `daily` | Inspection complète (base + services + sécurité + journaux) | 26 |

## 📊 Exemple de Rapport

```markdown
# 🔍 Rapport d'Inspection de Serveur

- Cible : production-web
- Hôte : IP_DE_VOTRE_SERVEUR
- Utilisateur : inspector
- Vérifications : daily
- Démarré : 2026-04-26T09:00:00+00:00
- Total des vérifications : 26
- ⚠️ Anomalies : 3

## État Global : ⚠️ AVERTISSEMENT

## ⚠️ Anomalies (Priorité)

### ⚠️ systemd_failed_units
Commande : `systemctl --failed --no-pager`
État : OK (contient des anomalies)

Sortie :
```
UNIT          LOAD   ACTIVE SUB    DESCRIPTION
mcelog.service loaded failed failed Machine Check Exception Logging Daemon
```
```

## 🔧 Options de Ligne de Commande

| Option | Description | Défaut |
|--------|-------------|--------|
| `--target` | Nom du serveur cible (défini dans targets.yaml) | (requis) |
| `--checks` | Groupe de vérification : `basic`, `services`, `daily` | `basic` |
| `--format` | Format de sortie : `markdown`, `json` | `markdown` |
| `--output` | Sortie dans un fichier (défaut : stdout) | stdout |

## 🌐 Prise en Charge Multi-Plateforme

| Plateforme | Environnement | Script | Commande |
|------------|---------------|--------|----------|
| **OpenClaw** | Node.js 24+ | `scripts/inspect.mjs` | `node scripts/inspect.mjs --target bogon --checks daily` |
| **NanoBot** | Python 3.10+ | `scripts/inspect.py` | `python3 scripts/inspect.py --target bogon --checks daily` |
| **Hermes** | Python 3.10+ | `scripts/inspect.py` | `python3 scripts/inspect.py --target bogon --checks daily` |

## 📁 Structure des Fichiers

```
li_sentry_check/
├── SKILL.md                  # Documentation de la compétence
├── _meta.json                # Métadonnées de la compétence
├── design.md                 # Documentation de conception
├── references/
│   ├── targets.yaml          # Configuration des serveurs cibles
│   └── checks.yaml           # Liste blanche des commandes d'inspection
└── scripts/
    ├── inspect.mjs           # Implémentation Node.js (OpenClaw)
    └── inspect.py            # Implémentation Python (NanoBot/Hermes)
```

## 🔒 Bonnes Pratiques de Sécurité

- **Permissions des clés** : `chmod 600 ~/.ssh/li_sentry_check`
- **Vérification de l'hôte** : Pour la production, pré-remplissez `known_hosts` au lieu d'utiliser `accept-new`
- **Noms de services** : Uniquement alphanumérique, tirets, tirets bas autorisés (validés avant utilisation)
- **Liste blanche des commandes** : Ne jamais modifier `checks.yaml` avec des commandes modifiant l'état
- **Gestion des rapports** : Les rapports peuvent contenir des données système — ne pas partager publiquement

## 🔧 Guide d'Extension

### Ajouter un Nouveau Serveur Cible

Modifier `references/targets.yaml` :

```yaml
targets:
  serveur-base-donnees:
    host: IP_DE_VOTRE_SERVEUR
    port: 22
    user: inspector
    keyPath: ~/.ssh/li_sentry_check
    services:
      - mysql
      - redis
```

### Ajouter un Nouveau Groupe de Vérification

Modifier `references/checks.yaml` :

```yaml
checks:
  base-de-donnees:
    description: Inspection de la base de données
    commands:
      - id: mysql_status
        cmd: "systemctl status mysql --no-pager | sed -n '1,20p'"
        timeoutSec: 10
      - id: mysql_connections
        cmd: "mysql -e 'SHOW STATUS LIKE \"Threads_connected\"' || true"
        timeoutSec: 15
```

## 📝 Historique des Versions

| Version | Date | Modifications |
|---------|------|---------------|
| 0.1.0 | 2026-04-26 | Version initiale : inspection de base, des services et complète |

## 📄 Licence

Licence MIT


path: README.it.md
content: # 🔍 li_sentry_check - Skill di Ispezione Server

> Skill multi-piattaforma per ispezione e health check dei server. Accesso SSH ai server Linux remoti tramite autenticazione a chiave, esecuzione di comandi di ispezione in sola lettura e generazione di report strutturati in Markdown.

[![Versione](https://img.shields.io/badge/versione-0.1.0-blue.svg)](https://clawhub.ai/skills/li_sentry_check)
[![Piattaforme](https://img.shields.io/badge/piattaforme-nanobot%20%7C%20OpenClaw%20%7C%20Hermes-green.svg)]()
[![Licenza](https://img.shields.io/badge/licenza-MIT-green.svg)](LICENSE)

## 📋 Panoramica

`li_sentry_check` è una skill di ispezione server multi-piattaforma che supporta **nanobot**, **OpenClaw** e **Hermes agent**. Si connette ai server Linux remoti tramite autenticazione a chiave SSH, esegue comandi di ispezione in sola lettura (CPU, memoria, disco, rete, servizi, sicurezza) e genera report Markdown strutturati con evidenziazione automatica delle anomalie.

## ✨ Funzionalità Principali

| Funzionalità | Descrizione |
|--------------|-------------|
| 🔐 Autenticazione a Chiave SSH | Solo autenticazione a chiave, accesso con password disabilitato, sicurezza rinforzata |
| 📊 Ispezione Hardware | CPU, memoria, disco, utilizzo della rete |
| 🖥️ Ispezione Servizi | Stato dei servizi chiave, log degli errori |
| 🛡️ Ispezione Sicurezza | Accessi SSH anomali, avvisi firewall, errori del kernel |
| 📝 Report Strutturati | Formato Markdown/JSON, anomalie prioritarie |
| 🌐 Multi-Piattaforma | Supporta nanobot, OpenClaw, Hermes |

## 🚀 Guida Rapida

### 1. Installare la Skill

```bash
# nanobot
./manage.sh skill install li_sentry_check

# OpenClaw
npx clawhub@latest install li_sentry_check

# Hermes
hermes skill install li_sentry_check
```

### 2. Configurare le Chiavi SSH

```bash
# Generare coppia di chiavi
ssh-keygen -t rsa -b 4096 -f ~/.ssh/li_sentry_check -N ""

# Copiare la chiave pubblica sul server remoto
ssh-copy-id -i ~/.ssh/li_sentry_check.pub inspector@<IP_SERVER>

# Testare la connessione
ssh -i ~/.ssh/li_sentry_check inspector@<IP_SERVER>
```

### 3. Configurare i Server Target

Modificare `references/targets.yaml`:

```yaml
targets:
  produzione-web:
    host: IL_TUO_IP_SERVER
    port: 22
    user: inspector
    keyPath: ~/.ssh/li_sentry_check
    services:
      - nginx
      - docker
      - sshd
```

### 4. Eseguire l'Ispezione

```bash
# Ispezione base (risorse hardware)
python3 scripts/inspect.py --target produzione-web --checks basic

# Ispezione servizi
python3 scripts/inspect.py --target produzione-web --checks services

# Ispezione completa (base + servizi + sicurezza + log)
python3 scripts/inspect.py --target produzione-web --checks daily

# Output in formato JSON
python3 scripts/inspect.py --target produzione-web --checks daily --format json

# Output su file
python3 scripts/inspect.py --target produzione-web --checks daily --output report.md
```

## 📖 Gruppi di Verifica Ispezione

| Gruppo | Contenuto | Comandi |
|--------|-----------|---------|
| `basic` | CPU, memoria, disco, rete | 8 |
| `services` | Stato servizi + log errori (dinamico) | 3×N |
| `daily` | Ispezione completa (base + servizi + sicurezza + log) | 26 |

## 📊 Esempio di Report

```markdown
# 🔍 Report Ispezione Server

- Target: produzione-web
- Host: IL_TUO_IP_SERVER
- Utente: inspector
- Verifiche: daily
- Avviato: 2026-04-26T09:00:00+00:00
- Totale verifiche: 26
- ⚠️ Anomalie: 3

## Stato Generale: ⚠️ AVVISO

## ⚠️ Anomalie (Priorità)

### ⚠️ systemd_failed_units
Comando: `systemctl --failed --no-pager`
Stato: OK (contiene anomalie)

Output:
```
UNIT          LOAD   ACTIVE SUB    DESCRIPTION
mcelog.service loaded failed failed Machine Check Exception Logging Daemon
```
```

## 🔧 Opzioni da Riga di Comando

| Opzione | Descrizione | Predefinito |
|---------|-------------|-------------|
| `--target` | Nome server target (definito in targets.yaml) | (obbligatorio) |
| `--checks` | Gruppo di verifica: `basic`, `services`, `daily` | `basic` |
| `--format` | Formato output: `markdown`, `json` | `markdown` |
| `--output` | Output su file (predefinito: stdout) | stdout |

## 🌐 Supporto Multi-Piattaforma

| Piattaforma | Runtime | Script | Comando |
|-------------|---------|--------|---------|
| **OpenClaw** | Node.js 24+ | `scripts/inspect.mjs` | `node scripts/inspect.mjs --target bogon --checks daily` |
| **NanoBot** | Python 3.10+ | `scripts/inspect.py` | `python3 scripts/inspect.py --target bogon --checks daily` |
| **Hermes** | Python 3.10+ | `scripts/inspect.py` | `python3 scripts/inspect.py --target bogon --checks daily` |

## 📁 Struttura dei File

```
li_sentry_check/
├── SKILL.md                  # Documentazione della skill
├── _meta.json                # Metadati della skill
├── design.md                 # Documentazione di design
├── references/
│   ├── targets.yaml          # Configurazione server target
│   └── checks.yaml           # Whitelist comandi di ispezione
└── scripts/
    ├── inspect.mjs           # Implementazione Node.js (OpenClaw)
    └── inspect.py            # Implementazione Python (NanoBot/Hermes)
```

## 🔒 Best Practice di Sicurezza

- **Permessi chiave**: `chmod 600 ~/.ssh/li_sentry_check`
- **Verifica host**: Per la produzione, pre-compilare `known_hosts` invece di usare `accept-new`
- **Nomi servizi**: Solo alfanumerico, trattini, underscore consentiti (validati prima dell'uso)
- **Whitelist comandi**: Non modificare mai `checks.yaml` con comandi che modificano lo stato
- **Gestione report**: I report possono contenere dati di sistema — non condividere pubblicamente

## 🔧 Guida all'Estensione

### Aggiungere un Nuovo Server Target

Modificare `references/targets.yaml`:

```yaml
targets:
  server-database:
    host: IL_TUO_IP_SERVER
    port: 22
    user: inspector
    keyPath: ~/.ssh/li_sentry_check
    services:
      - mysql
      - redis
```

### Aggiungere un Nuovo Gruppo di Verifica

Modificare `references/checks.yaml`:

```yaml
checks:
  database:
    description: Ispezione database
    commands:
      - id: mysql_status
        cmd: "systemctl status mysql --no-pager | sed -n '1,20p'"
        timeoutSec: 10
      - id: mysql_connections
        cmd: "mysql -e 'SHOW STATUS LIKE \"Threads_connected\"' || true"
        timeoutSec: 15
```

## 📝 Cronologia Versioni

| Versione | Data | Modifiche |
|----------|------|-----------|
| 0.1.0 | 2026-04-26 | Versione iniziale: ispezione base, servizi e completa |

## 📄 Licenza

Licenza MIT


path: README.ja.md
content: # 🔍 li_sentry_check - サーバーインスペクションスキル

> マルチプラットフォームサーバーインスペクション＆ヘルスチェックスキル。SSHキー認証でリモートLinuxサーバーにログインし、読み取り専用インスペクションコマンドを実行して、構造化されたMarkdownレポートを生成します。

[![バージョン](https://img.shields.io/badge/version-0.1.0-blue.svg)](https://clawhub.ai/skills/li_sentry_check)
[![プラットフォーム](https://img.shields.io/badge/プラットフォーム-nanobot%20%7C%20OpenClaw%20%7C%20Hermes-green.svg)]()
[![ライセンス](https://img.shields.io/badge/ライセンス-MIT-green.svg)](LICENSE)

## 📋 概要

`li_sentry_check`は**nanobot**、**OpenClaw**、**Hermes agent**をサポートするクロスプラットフォームサーバーインスペクションスキルです。SSHキー認証でリモートLinuxサーバーにログインし、読み取り専用インスペクションコマンド（CPU、メモリ、ディスク、ネットワーク、サービス、セキュリティ）を実行して、異常情報を自動的にハイライトする構造化されたMarkdownレポートを生成します。

## ✨ コア機能

| 機能 | 説明 |
|------|------|
| 🔐 SSHキー認証 | キー認証のみ、パスワードログイン無効化、セキュリティ強化 |
| 📊 ハードウェアインスペクション | CPU、メモリ、ディスク、ネットワーク使用量 |
| 🖥️ サービスインスペクション | 重要サービス状態、エラーログ |
| 🛡️ セキュリティインスペクション | SSH異常ログイン、ファイアウォールアラート、カーネルエラー |
| 📝 構造化レポート | Markdown/JSON形式、異常優先表示 |
| 🌐 クロスプラットフォーム | nanobot、OpenClaw、Hermesをサポート |

## 🚀 クイックスタート

### 1. スキルインストール

```bash
# nanobot
./manage.sh skill install li_sentry_check

# OpenClaw
npx clawhub@latest install li_sentry_check

# Hermes
hermes skill install li_sentry_check
```

### 2. SSHキー設定

```bash
# キーペア生成
ssh-keygen -t rsa -b 4096 -f ~/.ssh/li_sentry_check -N ""

# 公開キーをリモートサーバーにコピー
ssh-copy-id -i ~/.ssh/li_sentry_check.pub inspector@<サーバーIP>

# 接続テスト
ssh -i ~/.ssh/li_sentry_check inspector@<サーバーIP>
```

### 3. ターゲットサーバー設定

`references/targets.yaml`を編集：

```yaml
targets:
  プロダクション-web:
    host: YOUR_SERVER_IP
    port: 22
    user: inspector
    keyPath: ~/.ssh/li_sentry_check
    services:
      - nginx
      - docker
      - sshd
```

### 4. インスペクション実行

```bash
# ベーシックインスペクション（ハードウェアリソース）
python3 scripts/inspect.py --target プロダクション-web --checks basic

# サービスインスペクション
python3 scripts/inspect.py --target プロダクション-web --checks services

# 完全インスペクション（ベーシック + サービス + セキュリティ + ログ）
python3 scripts/inspect.py --target プロダクション-web --checks daily

# JSON形式出力
python3 scripts/inspect.py --target プロダクション-web --checks daily --format json

# ファイルに出力
python3 scripts/inspect.py --target プロダクション-web --checks daily --output report.md
```

## 📖 インスペクションチェックグループ

| グループ | 内容 | コマンド数 |
|----------|------|------------|
| `basic` | CPU、メモリ、ディスク、ネットワーク | 8 |
| `services` | サービス状態 + エラーログ（動的） | 3×N |
| `daily` | 完全インスペクション（ベーシック + サービス + セキュリティ + ログ） | 26 |

## 📊 レポート例

```markdown
# 🔍 サーバーインスペクションレポート

- ターゲット: プロダクション-web
- ホスト: YOUR_SERVER_IP
- ユーザー: inspector
- チェック: daily
- 開始: 2026-04-26T09:00:00+00:00
- 総チェック: 26
- ⚠️ 異常: 3

## 全体ステータス: ⚠️ 警告

## ⚠️ 異常（優先）

### ⚠️ systemd_failed_units
コマンド: `systemctl --failed --no-pager`
ステータス: OK（異常を含む）

出力:
```
UNIT          LOAD   ACTIVE SUB    DESCRIPTION
mcelog.service loaded failed failed Machine Check Exception Logging Daemon
```
```

## 🔧 コマンドラインオプション

| オプション | 説明 | デフォルト |
|------------|------|------------|
| `--target` | ターゲットサーバー名（targets.yamlで定義） | （必須） |
| `--checks` | チェックグループ: `basic`、`services`、`daily` | `basic` |
| `--format` | 出力形式: `markdown`、`json` | `markdown` |
| `--output` | ファイルに出力（デフォルト: stdout） | stdout |

## 🌐 クロスプラットフォームサポート

| プラットフォーム | ランタイム | スクリプト | コマンド |
|------------------|------------|------------|----------|
| **OpenClaw** | Node.js 24+ | `scripts/inspect.mjs` | `node scripts/inspect.mjs --target bogon --checks daily` |
| **NanoBot** | Python 3.10+ | `scripts/inspect.py` | `python3 scripts/inspect.py --target bogon --checks daily` |
| **Hermes** | Python 3.10+ | `scripts/inspect.py` | `python3 scripts/inspect.py --target bogon --checks daily` |

## 📁 ファイル構造

```
li_sentry_check/
├── SKILL.md                  # スキルドキュメント
├── _meta.json                # スキルメタデータ
├── design.md                 # デザインドキュメント
├── references/
│   ├── targets.yaml          # ターゲットサーバー設定
│   └── checks.yaml           # インスペクションコマンドホワイトリスト
└── scripts/
    ├── inspect.mjs           # Node.js実装（OpenClaw）
    └── inspect.py            # Python実装（NanoBot/Hermes）
```

## 🔒 セキュリティベストプラクティス

- **キー権限**: `chmod 600 ~/.ssh/li_sentry_check`
- **ホスト検証**: プロダクションでは`accept-new`の代わりに`known_hosts`を事前に設定
- **サービス名**: 英数字、ハイフン、アンダースコアのみ許可（使用前に検証）
- **コマンドホワイトリスト**: `checks.yaml`を状態変更コマンドで決して修正しない
- **レポート処理**: レポートにシステムデータが含まれる可能性があります — 公開で共有しないでください

## 🔧 拡張ガイド

### 新しいターゲットサーバー追加

`references/targets.yaml`を編集：

```yaml
targets:
  データベース-サーバー:
    host: YOUR_SERVER_IP
    port: 22
    user: inspector
    keyPath: ~/.ssh/li_sentry_check
    services:
      - mysql
      - redis
```

### 新しいチェックグループ追加

`references/checks.yaml`を編集：

```yaml
checks:
  データベース:
    description: データベースインスペクション
    commands:
      - id: mysql_status
        cmd: "systemctl status mysql --no-pager | sed -n '1,20p'"
        timeoutSec: 10
      - id: mysql_connections
        cmd: "mysql -e 'SHOW STATUS LIKE \"Threads_connected\"' || true"
        timeoutSec: 15
```

## 📝 バージョン履歴

| バージョン | 日付 | 変更 |
|------------|------|------|
| 0.1.0 | 2026-04-26 | 初期リリース: ベーシック、サービス、完全インスペクション |

## 📄 ライセンス

MITライセンス


path: README.ko.md
content: # 🔍 li_sentry_check - 서버 점검 스킬

> 멀티 플랫폼 서버 점검 및 헬스체크 스킬. SSH 키 인증을 통해 원격 Linux 서버에 로그인하여 읽기 전용 점검 명령을 실행하고 구조화된 Markdown 보고서를 생성합니다.

[![버전](https://img.shields.io/badge/version-0.1.0-blue.svg)](https://clawhub.ai/skills/li_sentry_check)
[![플랫폼](https://img.shields.io/badge/플랫폼-nanobot%20%7C%20OpenClaw%20%7C%20Hermes-green.svg)]()
[![라이선스](https://img.shields.io/badge/라이선스-MIT-green.svg)](LICENSE)

## 📋 개요

`li_sentry_check`는 **nanobot**, **OpenClaw**, **Hermes agent**를 지원하는 크로스 플랫폼 서버 점검 스킬입니다. SSH 키 인증을 통해 원격 Linux 서버에 로그인하여 읽기 전용 점검 명령(CPU, 메모리, 디스크, 네트워크, 서비스, 보안)을 실행하고 이상 정보를 자동으로 강조 표시하는 구조화된 Markdown 보고서를 생성합니다.

## ✨ 핵심 기능

| 기능 | 설명 |
|------|------|
| 🔐 SSH 키 인증 | 키 전용 인증, 비밀번호 로그인 비활성화, 보안 강화 |
| 📊 하드웨어 점검 | CPU, 메모리, 디스크, 네트워크 사용량 |
| 🖥️ 서비스 점검 | 주요 서비스 상태, 오류 로그 |
| 🛡️ 보안 점검 | SSH 비정상 로그인, 방화벽 경고, 커널 오류 |
| 📝 구조화된 보고서 | Markdown/JSON 형식, 이상 정보 우선 표시 |
| 🌐 크로스 플랫폼 | nanobot, OpenClaw, Hermes 지원 |

## 🚀 빠른 시작

### 1. 스킬 설치

```bash
# nanobot
./manage.sh skill install li_sentry_check

# OpenClaw
npx clawhub@latest install li_sentry_check

# Hermes
hermes skill install li_sentry_check
```

### 2. SSH 키 구성

```bash
# 키 쌍 생성
ssh-keygen -t rsa -b 4096 -f ~/.ssh/li_sentry_check -N ""

# 공개 키를 원격 서버에 복사
ssh-copy-id -i ~/.ssh/li_sentry_check.pub inspector@<서버_IP>

# 연결 테스트
ssh -i ~/.ssh/li_sentry_check inspector@<서버_IP>
```

### 3. 대상 서버 구성

`references/targets.yaml` 수정:

```yaml
targets:
  운영-웹:
    host: YOUR_SERVER_IP
    port: 22
    user: inspector
    keyPath: ~/.ssh/li_sentry_check
    services:
      - nginx
      - docker
      - sshd
```

### 4. 점검 실행

```bash
# 기본 점검 (하드웨어 리소스)
python3 scripts/inspect.py --target 운영-웹 --checks basic

# 서비스 점검
python3 scripts/inspect.py --target 운영-웹 --checks services

# 전체 점검 (기본 + 서비스 + 보안 + 로그)
python3 scripts/inspect.py --target 운영-웹 --checks daily

# JSON 형식 출력
python3 scripts/inspect.py --target 운영-웹 --checks daily --format json

# 파일로 출력
python3 scripts/inspect.py --target 운영-웹 --checks daily --output report.md
```

## 📖 점검 체크 그룹

| 그룹 | 내용 | 명령 수 |
|------|------|---------|
| `basic` | CPU, 메모리, 디스크, 네트워크 | 8 |
| `services` | 서비스 상태 + 오류 로그 (동적) | 3×N |
| `daily` | 전체 점검 (기본 + 서비스 + 보안 + 로그) | 26 |

## 📊 보고서 예시

```markdown
# 🔍 서버 점검 보고서

- 대상: 운영-웹
- 호스트: YOUR_SERVER_IP
- 사용자: inspector
- 체크: daily
- 시작: 2026-04-26T09:00:00+00:00
- 전체 체크: 26
- ⚠️ 이상: 3

## 전체 상태: ⚠️ 경고

## ⚠️ 이상 (우선)

### ⚠️ systemd_failed_units
명령: `systemctl --failed --no-pager`
상태: OK (이상 포함)

출력:
```
UNIT          LOAD   ACTIVE SUB    DESCRIPTION
mcelog.service loaded failed failed Machine Check Exception Logging Daemon
```
```

## 🔧 명령줄 옵션

| 옵션 | 설명 | 기본값 |
|------|------|--------|
| `--target` | 대상 서버 이름 (targets.yaml에 정의) | (필수) |
| `--checks` | 체크 그룹: `basic`, `services`, `daily` | `basic` |
| `--format` | 출력 형식: `markdown`, `json` | `markdown` |
| `--output` | 파일로 출력 (기본: stdout) | stdout |

## 🌐 크로스 플랫폼 지원

| 플랫폼 | 런타임 | 스크립트 | 명령 |
|--------|--------|----------|------|
| **OpenClaw** | Node.js 24+ | `scripts/inspect.mjs` | `node scripts/inspect.mjs --target bogon --checks daily` |
| **NanoBot** | Python 3.10+ | `scripts/inspect.py` | `python3 scripts/inspect.py --target bogon --checks daily` |
| **Hermes** | Python 3.10+ | `scripts/inspect.py` | `python3 scripts/inspect.py --target bogon --checks daily` |

## 📁 파일 구조

```
li_sentry_check/
├── SKILL.md                  # 스킬 문서
├── _meta.json                # 스킬 메타데이터
├── design.md                 # 설계 문서
├── references/
│   ├── targets.yaml          # 대상 서버 구성
│   └── checks.yaml           # 점검 명령 허용 목록
└── scripts/
    ├── inspect.mjs           # Node.js 구현 (OpenClaw)
    └── inspect.py            # Python 구현 (NanoBot/Hermes)
```

## 🔒 보안 모범 사례

- **키 권한**: `chmod 600 ~/.ssh/li_sentry_check`
- **호스트 검증**: 프로덕션에서는 `accept-new` 대신 `known_hosts`를 사전에 채우세요
- **서비스 이름**: 영숫자, 하이픈, 밑줄만 허용 (사용 전 검증)
- **명령 허용 목록**: `checks.yaml`을 상태 변경 명령으로 절대 수정하지 마세요
- **보고서 처리**: 보고서에 시스템 데이터가 포함될 수 있음 — 공개적으로 공유하지 마세요

## 🔧 확장 가이드

### 새 대상 서버 추가

`references/targets.yaml` 수정:

```yaml
targets:
  데이터베이스-서버:
    host: YOUR_SERVER_IP
    port: 22
    user: inspector
    keyPath: ~/.ssh/li_sentry_check
    services:
      - mysql
      - redis
```

### 새 체크 그룹 추가

`references/checks.yaml` 수정:

```yaml
checks:
  데이터베이스:
    description: 데이터베이스 점검
    commands:
      - id: mysql_status
        cmd: "systemctl status mysql --no-pager | sed -n '1,20p'"
        timeoutSec: 10
      - id: mysql_connections
        cmd: "mysql -e 'SHOW STATUS LIKE \"Threads_connected\"' || true"
        timeoutSec: 15
```

## 📝 버전 기록

| 버전 | 날짜 | 변경 사항 |
|------|------|-----------|
| 0.1.0 | 2026-04-26 | 초기 릴리스: 기본, 서비스, 전체 점검 |

## 📄 라이선스

MIT 라이선스


path: README.md
content: # 🔍 li_sentry_check - 服务器巡检技能

> 多平台服务器巡检与健康管理技能。通过 SSH 密钥认证登录远程 Linux 服务器，执行只读巡检命令，生成结构化 Markdown 报告。

[![Version](https://img.shields.io/badge/version-0.1.0-blue.svg)](https://clawhub.ai/skills/li_sentry_check)
[![Platforms](https://img.shields.io/badge/platforms-nanobot%20%7C%20OpenClaw%20%7C%20Hermes-green.svg)]()
[![License](https://img.shields.io/badge/license-MIT-green.svg)](LICENSE)

## 📋 概述

`li_sentry_check` 是一个跨平台服务器巡检技能，支持 **nanobot**、**OpenClaw** 和 **Hermes agent** 三大平台。通过 SSH 密钥认证登录远程 Linux 服务器，执行只读巡检命令（CPU、内存、磁盘、网络、服务、安全），生成结构化 Markdown 报告，自动突出异常信息。

## ✨ 核心功能

| 功能 | 说明 |
|------|------|
| 🔐 SSH 密钥认证 | 仅密钥认证，禁止密码登录，安全加固 |
| 📊 硬件巡检 | CPU、内存、磁盘、网络使用情况 |
| 🖥️ 服务巡检 | 重点服务运行状态、异常日志 |
| 🛡️ 安全巡检 | SSH 异常登录、防火墙告警、内核错误 |
| 📝 结构化报告 | Markdown/JSON 格式，异常优先显示 |
| 🌐 跨平台 | 支持 nanobot、OpenClaw、Hermes |

## 🚀 快速开始

### 1. 安装技能

```bash
# nanobot
./manage.sh skill install li_sentry_check

# OpenClaw
npx clawhub@latest install li_sentry_check

# Hermes
hermes skill install li_sentry_check
```

### 2. 配置 SSH 密钥

```bash
# 生成密钥对
ssh-keygen -t rsa -b 4096 -f ~/.ssh/li_sentry_check -N ""

# 复制公钥到远程服务器
ssh-copy-id -i ~/.ssh/li_sentry_check.pub inspector@<服务器IP>

# 测试连接
ssh -i ~/.ssh/li_sentry_check inspector@<服务器IP>
```

### 3. 配置目标服务器

编辑 `references/targets.yaml`：

```yaml
targets:
  production-web:
    host: YOUR_SERVER_IP
    port: 22
    user: inspector
    keyPath: ~/.ssh/li_sentry_check
    services:
      - nginx
      - docker
      - sshd
```

### 4. 执行巡检

```bash
# 基础巡检（硬件资源）
python3 scripts/inspect.py --target production-web --checks basic

# 服务巡检
python3 scripts/inspect.py --target production-web --checks services

# 完整巡检（基础 + 服务 + 安全 + 日志）
python3 scripts/inspect.py --target production-web --checks daily

# JSON 格式输出
python3 scripts/inspect.py --target production-web --checks daily --format json

# 输出到文件
python3 scripts/inspect.py --target production-web --checks daily --output report.md
```

## 📖 巡检检查组

| 检查组 | 内容 | 命令数 |
|--------|------|--------|
| `basic` | CPU、内存、磁盘、网络 | 8 |
| `services` | 服务状态 + 错误日志（动态） | 3×N |
| `daily` | 完整巡检（basic + services + 安全 + 日志） | 26 |

## 📊 报告示例

```markdown
# 🔍 Server Inspection Report

- Target: production-web
- Host: YOUR_SERVER_IP
- User: inspector
- Checks: daily
- Started: 2026-04-26T09:00:00+00:00
- Total checks: 26
- ⚠️ Anomalies: 3

## Overall Status: ⚠️ WARNING

## ⚠️ Anomalies (Priority)

### ⚠️ systemd_failed_units
Command: `systemctl --failed --no-pager`
Status: OK (contains anomalies)

Output:
```
UNIT          LOAD   ACTIVE SUB    DESCRIPTION
mcelog.service loaded failed failed Machine Check Exception Logging Daemon
```

## <details>📋 View all check results (26 total)</details>
```

## 🔧 命令行参数

| 参数 | 说明 | 默认值 |
|------|------|--------|
| `--target` | 目标服务器名称（targets.yaml 中定义） | （必填） |
| `--checks` | 检查组：`basic`、`services`、`daily` | `basic` |
| `--format` | 输出格式：`markdown`、`json` | `markdown` |
| `--output` | 输出到文件（默认 stdout） | stdout |

## 🌐 跨平台支持

| 平台 | 运行时 | 脚本 | 命令 |
|------|--------|------|------|
| **OpenClaw** | Node.js 24+ | `scripts/inspect.mjs` | `node scripts/inspect.mjs --target bogon --checks daily` |
| **NanoBot** | Python 3.10+ | `scripts/inspect.py` | `python3 scripts/inspect.py --target bogon --checks daily` |
| **Hermes** | Python 3.10+ | `scripts/inspect.py` | `python3 scripts/inspect.py --target bogon --checks daily` |

## 📁 文件结构

```
li_sentry_check/
├── SKILL.md                  # 技能说明文档
├── _meta.json                # 技能元数据
├── design.md                 # 设计文档
├── references/
│   ├── targets.yaml          # 目标服务器配置
│   └── checks.yaml           # 巡检命令白名单
└── scripts/
    ├── inspect.mjs           # Node.js 实现（OpenClaw）
    └── inspect.py            # Python 实现（NanoBot/Hermes）
```

## 🔒 安全最佳实践

- **密钥权限**: `chmod 600 ~/.ssh/li_sentry_check`
- **主机验证**: 生产环境建议预填充 `known_hosts`，而非使用 `accept-new`
- **服务名称**: 仅允许字母、数字、连字符、下划线（使用前已验证）
- **命令白名单**: 永远不要在 `checks.yaml` 中添加状态修改命令
- **报告处理**: 报告可能包含系统数据 — 请勿公开分享

## 🔧 扩展指南

### 添加新目标服务器

编辑 `references/targets.yaml`：

```yaml
targets:
  database-server:
    host: YOUR_SERVER_IP
    port: 22
    user: inspector
    keyPath: ~/.ssh/li_sentry_check
    services:
      - mysql
      - redis
```

### 添加新检查组

编辑 `references/checks.yaml`：

```yaml
checks:
  database:
    description: 数据库巡检
    commands:
      - id: mysql_status
        cmd: "systemctl status mysql --no-pager | sed -n '1,20p'"
        timeoutSec: 10
      - id: mysql_connections
        cmd: "mysql -e 'SHOW STATUS LIKE \"Threads_connected\"' || true"
        timeoutSec: 15
```

## 📝 版本历史

| 版本 | 日期 | 变更 |
|------|------|------|
| 0.1.0 | 2026-04-26 | 初始版本：基础巡检、服务巡检、完整巡检 |

## 📄 许可证

MIT License


path: README.pt.md
content: # 🔍 li_sentry_check - Habilidade de Inspeção de Servidores

> Habilidade multiplataforma de inspeção e verificação de saúde de servidores. Acesso SSH a servidores Linux remotos por meio de autenticação por chave, execução de comandos de inspeção somente leitura e geração de relatórios estruturados em Markdown.

[![Versão](https://img.shields.io/badge/versão-0.1.0-blue.svg)](https://clawhub.ai/skills/li_sentry_check)
[![Plataformas](https://img.shields.io/badge/plataformas-nanobot%20%7C%20OpenClaw%20%7C%20Hermes-green.svg)]()
[![Licença](https://img.shields.io/badge/licença-MIT-green.svg)](LICENSE)

## 📋 Visão Geral

`li_sentry_check` é uma habilidade de inspeção de servidores multiplataforma que suporta **nanobot**, **OpenClaw** e **Hermes agent**. Ele se conecta a servidores Linux remotos por meio de autenticação por chave SSH, executa comandos de inspeção somente leitura (CPU, memória, disco, rede, serviços, segurança) e gera relatórios Markdown estruturados com destaque automático de anomalias.

## ✨ Funcionalidades Principais

| Funcionalidade | Descrição |
|----------------|-----------|
| 🔐 Autenticação por Chave SSH | Somente autenticação por chave, acesso por senha desabilitado, segurança reforçada |
| 📊 Inspeção de Hardware | CPU, memória, disco, uso de rede |
| 🖥️ Inspeção de Serviços | Estado de serviços-chave, logs de erros |
| 🛡️ Inspeção de Segurança | Logins SSH anômalos, alertas de firewall, erros do kernel |
| 📝 Relatórios Estruturados | Formato Markdown/JSON, anomalias prioritárias |
| 🌐 Multiplataforma | Suporta nanobot, OpenClaw, Hermes |

## 🚀 Início Rápido

### 1. Instalar a Habilidade

```bash
# nanobot
./manage.sh skill install li_sentry_check

# OpenClaw
npx clawhub@latest install li_sentry_check

# Hermes
hermes skill install li_sentry_check
```

### 2. Configurar Chaves SSH

```bash
# Gerar par de chaves
ssh-keygen -t rsa -b 4096 -f ~/.ssh/li_sentry_check -N ""

# Copiar chave pública para o servidor remoto
ssh-copy-id -i ~/.ssh/li_sentry_check.pub inspector@<IP_SERVIDOR>

# Testar conexão
ssh -i ~/.ssh/li_sentry_check inspector@<IP_SERVIDOR>
```

### 3. Configurar Servidores Alvo

Editar `references/targets.yaml`:

```yaml
targets:
  produção-web:
    host: SEU_IP_SERVIDOR
    port: 22
    user: inspector
    keyPath: ~/.ssh/li_sentry_check
    services:
      - nginx
      - docker
      - sshd
```

### 4. Executar Inspeção

```bash
# Inspeção básica (recursos de hardware)
python3 scripts/inspect.py --target produção-web --checks basic

# Inspeção de serviços
python3 scripts/inspect.py --target produção-web --checks services

# Inspeção completa (básica + serviços + segurança + logs)
python3 scripts/inspect.py --target produção-web --checks daily

# Saída em formato JSON
python3 scripts/inspect.py --target produção-web --checks daily --format json

# Saída para arquivo
python3 scripts/inspect.py --target produção-web --checks daily --output relatorio.md
```

## 📖 Grupos de Verificação de Inspeção

| Grupo | Conteúdo | Comandos |
|-------|----------|----------|
| `basic` | CPU, memória, disco, rede | 8 |
| `services` | Estado de serviços + logs de erros (dinâmico) | 3×N |
| `daily` | Inspeção completa (básica + serviços + segurança + logs) | 26 |

## 📊 Exemplo de Relatório

```markdown
# 🔍 Relatório de Inspeção de Servidor

- Alvo: produção-web
- Host: SEU_IP_SERVIDOR
- Usuário: inspector
- Verificações: daily
- Iniciado: 2026-04-26T09:00:00+00:00
- Total de verificações: 26
- ⚠️ Anomalias: 3

## Estado Geral: ⚠️ AVISO

## ⚠️ Anomalias (Prioridade)

### ⚠️ systemd_failed_units
Comando: `systemctl --failed --no-pager`
Estado: OK (contém anomalias)

Saída:
```
UNIT          LOAD   ACTIVE SUB    DESCRIPTION
mcelog.service loaded failed failed Machine Check Exception Logging Daemon
```
```

## 🔧 Opções de Linha de Comando

| Opção | Descrição | Padrão |
|-------|-----------|--------|
| `--target` | Nome do servidor alvo (definido em targets.yaml) | (obrigatório) |
| `--checks` | Grupo de verificação: `basic`, `services`, `daily` | `basic` |
| `--format` | Formato de saída: `markdown`, `json` | `markdown` |
| `--output` | Saída para arquivo (padrão: stdout) | stdout |

## 🌐 Suporte Multiplataforma

| Plataforma | Runtime | Script | Comando |
|------------|---------|--------|---------|
| **OpenClaw** | Node.js 24+ | `scripts/inspect.mjs` | `node scripts/inspect.mjs --target bogon --checks daily` |
| **NanoBot** | Python 3.10+ | `scripts/inspect.py` | `python3 scripts/inspect.py --target bogon --checks daily` |
| **Hermes** | Python 3.10+ | `scripts/inspect.py` | `python3 scripts/inspect.py --target bogon --checks daily` |

## 📁 Estrutura de Arquivos

```
li_sentry_check/
├── SKILL.md                  # Documentação da habilidade
├── _meta.json                # Metadados da habilidade
├── design.md                 # Documentação de design
├── references/
│   ├── targets.yaml          # Configuração de servidores alvo
│   └── checks.yaml           # Lista branca de comandos de inspeção
└── scripts/
    ├── inspect.mjs           # Implementação Node.js (OpenClaw)
    └── inspect.py            # Implementação Python (NanoBot/Hermes)
```

## 🔒 Melhores Práticas de Segurança

- **Permissões de chave**: `chmod 600 ~/.ssh/li_sentry_check`
- **Verificação de host**: Para produção, pré-preencha `known_hosts` em vez de usar `accept-new`
- **Nomes de serviços**: Apenas alfanumérico, hífens, sublinhados permitidos (validados antes do uso)
- **Lista branca de comandos**: Nunca modifique `checks.yaml` com comandos que alterem o estado
- **Manuseio de relatórios**: Os relatórios podem conter dados do sistema — não compartilhe publicamente

## 🔧 Guia de Extensão

### Adicionar um Novo Servidor Alvo

Editar `references/targets.yaml`:

```yaml
targets:
  servidor-banco-dados:
    host: SEU_IP_SERVIDOR
    port: 22
    user: inspector
    keyPath: ~/.ssh/li_sentry_check
    services:
      - mysql
      - redis
```

### Adicionar um Novo Grupo de Verificação

Editar `references/checks.yaml`:

```yaml
checks:
  banco-dados:
    description: Inspeção de banco de dados
    commands:
      - id: mysql_status
        cmd: "systemctl status mysql --no-pager | sed -n '1,20p'"
        timeoutSec: 10
      - id: mysql_connections
        cmd: "mysql -e 'SHOW STATUS LIKE \"Threads_connected\"' || true"
        timeoutSec: 15
```

## 📝 Histórico de Versões

| Versão | Data | Alterações |
|--------|------|------------|
| 0.1.0 | 2026-04-26 | Versão inicial: inspeção básica, de serviços e completa |

## 📄 Licença

Licença MIT


path: README.ru.md
content: # 🔍 li_sentry_check - Навык инспекции серверов

> Кроссплатформенный навык инспекции и проверки здоровья серверов. Подключение SSH к удалённым Linux-серверам с аутентификацией по ключу, выполнение команд инспекции только для чтения и генерация структурированных отчётов в Markdown.

[![Версия](https://img.shields.io/badge/версия-0.1.0-blue.svg)](https://clawhub.ai/skills/li_sentry_check)
[![Платформы](https://img.shields.io/badge/платформы-nanobot%20%7C%20OpenClaw%20%7C%20Hermes-green.svg)]()
[![Лицензия](https://img.shields.io/badge/лицензия-MIT-green.svg)](LICENSE)

## 📋 Обзор

`li_sentry_check` — это кроссплатформенный навык инспекции серверов, поддерживающий **nanobot**, **OpenClaw** и **Hermes agent**. Он подключается к удалённым Linux-серверам через SSH-аутентификацию по ключу, выполняет команды инспекции только для чтения (CPU, память, диск, сеть, сервисы, безопасность) и генерирует структурированные Markdown-отчёты с автоматическим выделением аномалий.

## ✨ Основные функции

| Функция | Описание |
|---------|----------|
| 🔐 SSH-аутентификация по ключу | Только аутентификация по ключу, вход по паролю отключён, безопасность усилена |
| 📊 Инспекция оборудования | CPU, память, диск, использование сети |
| 🖥️ Инспекция сервисов | Состояние ключевых сервисов, журналы ошибок |
| 🛡️ Инспекция безопасности | Аномальные SSH-входы, предупреждения фаервола, ошибки ядра |
| 📝 Структурированные отчёты | Формат Markdown/JSON, аномалии в приоритете |
| 🌐 Кроссплатформенность | Поддерживает nanobot, OpenClaw, Hermes |

## 🚀 Быстрый старт

### 1. Установка навыка

```bash
# nanobot
./manage.sh skill install li_sentry_check

# OpenClaw
npx clawhub@latest install li_sentry_check

# Hermes
hermes skill install li_sentry_check
```

### 2. Настройка SSH-ключей

```bash
# Генерация пары ключей
ssh-keygen -t rsa -b 4096 -f ~/.ssh/li_sentry_check -N ""

# Копирование открытого ключа на удалённый сервер
ssh-copy-id -i ~/.ssh/li_sentry_check.pub inspector@<IP_СЕРВЕРА>

# Тест подключения
ssh -i ~/.ssh/li_sentry_check inspector@<IP_СЕРВЕРА>
```

### 3. Настройка целевых серверов

Редактировать `references/targets.yaml`:

```yaml
targets:
  production-web:
    host: ВАШ_IP_СЕРВЕРА
    port: 22
    user: inspector
    keyPath: ~/.ssh/li_sentry_check
    services:
      - nginx
      - docker
      - sshd
```

### 4. Запуск инспекции

```bash
# Базовая инспекция (аппаратные ресурсы)
python3 scripts/inspect.py --target production-web --checks basic

# Инспекция сервисов
python3 scripts/inspect.py --target production-web --checks services

# Полная инспекция (базовая + сервисы + безопасность + журналы)
python3 scripts/inspect.py --target production-web --checks daily

# Вывод в формате JSON
python3 scripts/inspect.py --target production-web --checks daily --format json

# Вывод в файл
python3 scripts/inspect.py --target production-web --checks daily --output report.md
```

## 📖 Группы проверок инспекции

| Группа | Содержимое | Команды |
|--------|------------|---------|
| `basic` | CPU, память, диск, сеть | 8 |
| `services` | Состояние сервисов + журналы ошибок (динамически) | 3×N |
| `daily` | Полная инспекция (базовая + сервисы + безопасность + журналы) | 26 |

## 📊 Пример отчёта

```markdown
# 🔍 Отчёт об инспекции сервера

- Цель: production-web
- Хост: ВАШ_IP_СЕРВЕРА
- Пользователь: inspector
- Проверки: daily
- Запущен: 2026-04-26T09:00:00+00:00
- Всего проверок: 26
- ⚠️ Аномалий: 3

## Общий статус: ⚠️ ПРЕДУПРЕЖДЕНИЕ

## ⚠️ Аномалии (Приоритет)

### ⚠️ systemd_failed_units
Команда: `systemctl --failed --no-pager`
Статус: OK (содержит аномалии)

Вывод:
```
UNIT          LOAD   ACTIVE SUB    DESCRIPTION
mcelog.service loaded failed failed Machine Check Exception Logging Daemon
```
```

## 🔧 Параметры командной строки

| Параметр | Описание | По умолчанию |
|----------|----------|--------------|
| `--target` | Имя целевого сервера (определён в targets.yaml) | (обязательно) |
| `--checks` | Группа проверок: `basic`, `services`, `daily` | `basic` |
| `--format` | Формат вывода: `markdown`, `json` | `markdown` |
| `--output` | Вывод в файл (по умолчанию: stdout) | stdout |

## 🌐 Кроссплатформенная поддержка

| Платформа | Среда выполнения | Скрипт | Команда |
|-----------|------------------|--------|---------|
| **OpenClaw** | Node.js 24+ | `scripts/inspect.mjs` | `node scripts/inspect.mjs --target bogon --checks daily` |
| **NanoBot** | Python 3.10+ | `scripts/inspect.py` | `python3 scripts/inspect.py --target bogon --checks daily` |
| **Hermes** | Python 3.10+ | `scripts/inspect.py` | `python3 scripts/inspect.py --target bogon --checks daily` |

## 📁 Структура файлов

```
li_sentry_check/
├── SKILL.md                  # Документация навыка
├── _meta.json                # Метаданные навыка
├── design.md                 # Документация дизайна
├── references/
│   ├── targets.yaml          # Настройка целевых серверов
│   └── checks.yaml           # Белый список команд инспекции
└── scripts/
    ├── inspect.mjs           # Реализация на Node.js (OpenClaw)
    └── inspect.py            # Реализация на Python (NanoBot/Hermes)
```

## 🔒 Лучшие практики безопасности

- **Права на ключ**: `chmod 600 ~/.ssh/li_sentry_check`
- **Проверка хоста**: Для продакшена предварительно заполните `known_hosts` вместо использования `accept-new`
- **Имена сервисов**: Только буквенно-цифровые символы, дефисы, подчёркивания (проверяются перед использованием)
- **Белый список команд**: Никогда не модифицируйте `checks.yaml` командами, изменяющими состояние
- **Обработка отчётов**: Отчёты могут содержать системные данные — не публикуйте их публично

## 🔧 Руководство по расширению

### Добавление нового целевого сервера

Редактировать `references/targets.yaml`:

```yaml
targets:
  сервер-базы-данных:
    host: ВАШ_IP_СЕРВЕРА
    port: 22
    user: inspector
    keyPath: ~/.ssh/li_sentry_check
    services:
      - mysql
      - redis
```

### Добавление новой группы проверок

Редактировать `references/checks.yaml`:

```yaml
checks:
  база-данных:
    description: Инспекция базы данных
    commands:
      - id: mysql_status
        cmd: "systemctl status mysql --no-pager | sed -n '1,20p'"
        timeoutSec: 10
      - id: mysql_connections
        cmd: "mysql -e 'SHOW STATUS LIKE \"Threads_connected\"' || true"
        timeoutSec: 15
```

## 📝 История версий

| Версия | Дата | Изменения |
|--------|------|-----------|
| 0.1.0 | 2026-04-26 | Первоначальный релиз: базовая, сервисная и полная инспекция |

## 📄 Лицензия

Лицензия MIT


path: _meta.json
content: {
  "ownerId": "kn7b7sdwcjy1etamx2zvahc5xx80k8d4",
  "slug": "li_sentry_check",
  "version": "0.3.0",
  "publishedAt": 1770691893078,
  "security": {
    "read_only": true,
    "network_access": "ssh_only",
    "file_access": ["references/targets.yaml", "references/checks.yaml", "SSH key path from keyPath"],
    "no_exfiltration": true,
    "command_allowlist": "references/checks.yaml"
  }
}


path: design.md
content: # li_sentry_check Skill 设计文档

## 概述

`li_sentry_check` 是一个跨平台服务器巡检技能，支持 nanobot、OpenClaw 和 Hermes agent 三大平台。通过 SSH 密钥认证登录远程 Linux 服务器，执行只读巡检命令，生成结构化 Markdown 报告。

## 架构设计

### 双引擎架构

| 平台 | 引擎 | 运行时 | 脚本 |
|------|------|--------|------|
| OpenClaw | Node.js | Node.js 24+ | `scripts/inspect.mjs` |
| NanoBot | Python | Python 3.10+ | `scripts/inspect.py` |
| Hermes | Python | Python 3.10+ | `scripts/inspect.py` |

### 文件结构

```
li_sentry_check/
├── SKILL.md                  # 技能说明文档（大脑）
├── _meta.json                # 技能元数据
├── references/
│   ├── targets.yaml          # 目标服务器配置
│   └── checks.yaml           # 巡检命令白名单
└── scripts/
    ├── inspect.mjs           # Node.js 实现（OpenClaw）
    └── inspect.py            # Python 实现（NanoBot/Hermes）
```

## 核心功能

### 1. SSH 密钥认证

- 使用 SSH 密钥对认证，禁止密码登录
- 支持自定义密钥路径
- 非交互式 SSH（BatchMode=yes）
- 连接超时保护（ConnectTimeout=8）

### 2. 巡检命令白名单

所有巡检命令在 `checks.yaml` 中定义，分为三组：

| 检查组 | 内容 |
|--------|------|
| `basic` | 硬件资源：CPU、内存、磁盘、网络 |
| `services` | 服务状态：systemctl status + 错误日志 |
| `daily` | 完整巡检：basic + services + 安全 + 日志 |

### 3. 动态命令生成

`services` 和 `daily` 检查组的命令根据 `targets.yaml` 中配置的服务动态生成：

```yaml
targets:
  bogon:
    services:
      - sshd
      - mongod
      - docker
```

自动为每个服务生成：
- `svc_<name>_status` — systemctl status
- `svc_<name>_errors` — journalctl 错误日志
- `svc_<name>_recent` — 最近日志（过滤异常关键词）

### 4. 异常检测与报告

报告包含异常关键词检测：
- failed, error, alert, critical
- WARNING, panic, segfault, oom
- killed process, no space, disk quota
- read-only, corrupt, timeout
- refused, denied, unreachable

报告结构：
```
# 🔍 Server Inspection Report
- Target: bogon
- Host: `YOUR_SERVER_IP`
- Overall Status: ⚠️ WARNING
- Anomalies: 3

## ⚠️ Anomalies (Priority)
### ⚠️ systemd_failed_units
...

## <details>View all check results (20 total)</details>
```

## 安全设计

### 只读原则

- 仅执行只读命令（whoami, uptime, free, df, ss 等）
- 禁止修改服务器配置
- 禁止安装软件
- 禁止重启服务

### SSH 安全

- 仅密钥认证，禁止密码
- BatchMode=yes 防止交互式提示
- StrictHostKeyChecking=accept-new 自动接受新主机
- ConnectTimeout=8 防止长时间挂起

### 命令白名单

- 所有命令在 `checks.yaml` 中预定义
- 不支持任意远程命令执行
- 每个命令有超时限制

## 使用方式

### NanoBot / Hermes

```bash
python3 scripts/inspect.py --target bogon --checks daily
python3 scripts/inspect.py --target bogon --checks basic --format json
python3 scripts/inspect.py --target bogon --checks daily --output report.md
```

### OpenClaw

```bash
node scripts/inspect.mjs --target bogon --checks daily
node scripts/inspect.mjs --target bogon --checks basic --format json
node scripts/inspect.mjs --target bogon --checks daily --output report.md
```

## SSH 密钥配置

```bash
# 1. 生成密钥对
ssh-keygen -t rsa -b 4096 -f ~/.ssh/li_sentry_check -N ""

# 2. 复制公钥到远程服务器
ssh-copy-id -i ~/.ssh/li_sentry_check.pub inspector@YOUR_SERVER_IP

# 3. 测试连接
ssh -i ~/.ssh/li_sentry_check inspector@YOUR_SERVER_IP

# 4. 配置 targets.yaml
# 更新 keyPath 为实际密钥路径
```

## 扩展指南

### 添加新目标服务器

编辑 `references/targets.yaml`：

```yaml
targets:
  server2:
    host: YOUR_SERVER_IP_2
    port: 22
    user: inspector
    keyPath: ~/.ssh/li_sentry_check
    services:
      - nginx
      - mysql
      - redis
```

### 添加新检查组

编辑 `references/checks.yaml`：

```yaml
checks:
  database:
    description: 数据库巡检
    commands:
      - id: mongo_status
        cmd: "mongosh --eval 'db.runCommand({ serverStatus: 1 }).ok' || true"
        timeoutSec: 20
```

## 版本历史

| 版本 | 日期 | 变更 |
|------|------|------|
| 0.1.0 | 2026-04-26 | 初始版本：基础巡检、服务巡检、完整巡检 |


path: references/checks.yaml
content: # ============================================================
# li_sentry_check - Inspection Command Allowlist
# ============================================================
# All commands are READ-ONLY. No state-changing commands allowed.
# Each command has: id, cmd, timeoutSec
# ============================================================

checks:
  basic:
    description: Hardware resources and system basics
    commands:
      - id: basic_identity
        cmd: whoami; hostname; uname -r; date -Is
        timeoutSec: 5
      - id: basic_uptime
        cmd: uptime
        timeoutSec: 5
      - id: basic_os
        cmd: cat /etc/os-release | sed -n '1,12p'
        timeoutSec: 5
      - id: hw_cpu
        cmd: "(command -v mpstat >/dev/null 2>&1 && mpstat -P ALL 1 3 | sed -n '1,160p') || (top -b -n1 | sed -n '1,25p') || true"
        timeoutSec: 15
      - id: hw_mem
        cmd: "free -h; echo; cat /proc/meminfo | egrep -i '^(MemTotal|MemFree|MemAvailable|Buffers|Cached|SwapTotal|SwapFree|Dirty|Writeback|Slab):' || true"
        timeoutSec: 10
      - id: hw_disk_fs
        cmd: df -hT | sed -n '1,25p'
        timeoutSec: 10
      - id: hw_disk_io
        cmd: "(command -v iostat >/dev/null 2>&1 && iostat -x 1 3 | sed -n '1,120p') || true"
        timeoutSec: 18
      - id: hw_net_overview
        cmd: ss -s | sed -n '1,80p'
        timeoutSec: 10

  services:
    description: Service status and error logs (dynamically generated from targets.yaml)
    commands: []

  daily:
    description: Full daily inspection (basic + services + security + logs)
    commands:
      - id: basic_identity
        cmd: whoami; hostname; uname -r; date -Is
        timeoutSec: 5
      - id: basic_uptime
        cmd: uptime
        timeoutSec: 5
      - id: basic_os
        cmd: cat /etc/os-release | sed -n '1,12p'
        timeoutSec: 5
      - id: hw_cpu
        cmd: "(command -v mpstat >/dev/null 2>&1 && mpstat -P ALL 1 3 | sed -n '1,160p') || (top -b -n1 | sed -n '1,25p') || true"
        timeoutSec: 15
      - id: hw_mem
        cmd: "free -h; echo; cat /proc/meminfo | egrep -i '^(MemTotal|MemFree|MemAvailable|Buffers|Cached|SwapTotal|SwapFree|Dirty|Writeback|Slab):' || true"
        timeoutSec: 10
      - id: hw_disk_fs
        cmd: df -hT | sed -n '1,25p'
        timeoutSec: 10
      - id: hw_disk_io
        cmd: "(command -v iostat >/dev/null 2>&1 && iostat -x 1 3 | sed -n '1,120p') || true"
        timeoutSec: 18
      - id: hw_net_overview
        cmd: ss -s | sed -n '1,80p'
        timeoutSec: 10
      - id: logs_journal_err_24h
        cmd: journalctl -p err..alert -S -24h --no-pager | tail -n 200 || true
        timeoutSec: 20
      - id: logs_dmesg_key
        cmd: "dmesg -T 2>/dev/null | egrep -i 'error|fail|oom|killed process|segfault|panic|xfs|ext4|nvme|reset|link down|call trace' | tail -n 200 || true"
        timeoutSec: 12
      - id: sec_last_failed
        cmd: lastb -n 50 2>/dev/null | sed -n '1,60p' || true
        timeoutSec: 12
      - id: sec_sshd_suspicious_24h
        cmd: "journalctl -u sshd -S -24h --no-pager | egrep -i 'failed password|invalid user|authentication failure|maximum authentication attempts|POSSIBLE BREAK-IN ATTEMPT|Did not receive identification string|Connection closed by authenticating user|error: kex_exchange_identification' | tail -n 200 || true"
        timeoutSec: 20
      - id: systemd_failed_units
        cmd: systemctl --failed --no-pager || true
        timeoutSec: 10
      - id: systemd_recent_errors
        cmd: journalctl -p err..alert -n 80 --no-pager || true
        timeoutSec: 15


path: references/targets.yaml
content: # ============================================================
# li_sentry_check - Target Server Configuration
# ============================================================
# Add your target servers here. Each target needs:
#   - host: Server IP or hostname
#   - port: SSH port (default: 22)
#   - user: SSH username (recommend: dedicated inspector account)
#   - keyPath: Path to SSH private key
#   - services: List of services to monitor (optional)
# ============================================================

targets:
  bogon:
    host: YOUR_SERVER_IP
    port: 22
    user: inspector
    keyPath: ~/.ssh/li_sentry_check
    services:
      - sshd
      - mongod
      - docker
      - firewalld

  # Example: Add more targets
  # server2:
  #   host: YOUR_SERVER_IP_2
  #   port: 22
  #   user: inspector
  #   keyPath: ~/.ssh/li_sentry_check
  #   services:
  #     - nginx
  #     - mysql
  #     - redis


path: scripts/inspect.mjs
content: #!/usr/bin/env node
/*
  li_sentry_check - Multi-platform server inspection (Node.js version)
  - Loads targets from references/targets.yaml
  - Loads allowlisted checks from references/checks.yaml
  - Runs each command over SSH (non-interactive), captures stdout/stderr
  - Prints a Markdown report with anomaly highlighting
  Compatible with OpenClaw.

  SECURITY CONSTRAINTS:
  - ONLY reads from: references/targets.yaml, references/checks.yaml, SSH key
  - ONLY connects to ONE server via SSH (target specified in targets.yaml)
  - ONLY executes commands from references/checks.yaml allowlist
  - NEVER modifies server state, installs software, or writes files
  - NEVER exfiltrates data to external services
  - NEVER executes arbitrary commands
*/
import { execFile } from 'node:child_process';
import { readFile } from 'node:fs/promises';
import { fileURLToPath } from 'node:url';
import { dirname, join } from 'node:path';

// SECURITY: Only these files are read
const ALLOWED_FILES = [
  'references/targets.yaml',
  'references/checks.yaml',
];

// SECURITY: Only SSH connections are made (no HTTP, no external APIs)
// SECURITY: Only commands from checks.yaml are executed
// SECURITY: No state changes on remote servers (read-only)

// Error keywords for anomaly detection
const ERROR_KEYWORDS = [
  'failed', 'error', 'alert', 'critical', 'SELinux is preventing',
  'WARNING', 'panic', 'segfault', 'oom', 'killed process',
  'no space', 'disk quota', 'read-only', 'corrupt', 'timeout',
  'refused', 'denied', 'unreachable', 'broken pipe', 'i/o error',
];

function usage() {
  console.log(`Usage:
  node scripts/inspect.mjs --target <name> --checks <group> [--format markdown|json]
Options:
  --target   Target name in references/targets.yaml
  --checks   Check group in references/checks.yaml (default: basic)
  --format   Output format: markdown, json (default: markdown)
  --output   Write report to file instead of stdout
`);
}

function parseArgs(argv) {
  const args = { checks: 'basic', format: 'markdown' };
  for (let i = 2; i < argv.length; i++) {
    const a = argv[i];
    if (a === '--help' || a === '-h') { args.help = true; }
    else if (a === '--target') args.target = argv[++i];
    else if (a === '--checks') args.checks = argv[++i];
    else if (a === '--format') args.format = argv[++i];
    else if (a === '--output') args.output = argv[++i];
    else { throw new Error(`Unknown arg: ${a}`); }
  }
  return args;
}

function parseSimpleYaml(text) {
  const lines = text.replace(/\r\n/g, '\n').split('\n');
  const root = {};
  const stack = [{ indent: -1, obj: root }];
  for (let idx = 0; idx < lines.length; idx++) {
    const raw = lines[idx];
    const line = raw.replace(/\t/g, '  ');
    if (!line.trim() || line.trim().startsWith('#')) continue;
    const indent = line.match(/^ */)[0].length;
    while (stack.length && indent <= stack[stack.length - 1].indent) stack.pop();
    const parent = stack[stack.length - 1].obj;
    const trimmed = line.trim();
    if (trimmed.startsWith('- ')) {
      if (!Array.isArray(parent)) throw new Error('YAML list item in non-list');
      parent.push(stripQuotes(trimmed.slice(2).trim()));
      continue;
    }
    const [k, ...rest] = trimmed.split(':');
    const key = k.trim();
    const value = rest.join(':').trim();
    if (value === '') {
      let j = idx + 1;
      let next = null;
      while (j < lines.length) {
        const nl = lines[j].replace(/\t/g, '  ');
        const nt = nl.trim();
        if (nt && !nt.startsWith('#')) {
          next = { indent: nl.match(/^ */)[0].length, trimmed: nt };
          break;
        }
        j++;
      }
      const isList = next && next.indent > indent && next.trimmed.startsWith('- ');
      const container = isList ? [] : {};
      parent[key] = container;
      stack.push({ indent, obj: container });
    } else {
      parent[key] = stripQuotes(value);
    }
  }
  return root;
}

function stripQuotes(s) {
  if ((s.startsWith('"') && s.endsWith('"')) || (s.startsWith("'") && s.endsWith("'"))) {
    return s.slice(1, -1);
  }
  if (/^\d+$/.test(s)) return Number(s);
  return s;
}

function execFileP(cmd, args, { timeoutMs } = {}) {
  return new Promise((resolve) => {
    execFile(cmd, args, { timeout: timeoutMs, maxBuffer: 10 * 1024 * 1024 }, (error, stdout, stderr) => {
      resolve({ error, stdout: stdout ?? '', stderr: stderr ?? '' });
    });
  });
}

function mdEscape(s) {
  return s.replace(/`/g, '\\`');
}

function nowIso() {
  return new Date().toISOString();
}

function hasAnomaly(stdout, stderr) {
  const combined = (stdout + stderr).toLowerCase();
  return ERROR_KEYWORDS.some(kw => combined.includes(kw.toLowerCase()));
}

function buildServiceCommands(services) {
  const out = [];
  const uniq = [...new Set((services || []).map(s => String(s).trim()).filter(Boolean))];
  for (const name of uniq) {
    // Validate service name to prevent command injection
    if (!/^[a-zA-Z0-9_-]+$/.test(name)) {
      out.push({
        id: `svc_${name.replace(/[^a-zA-Z0-9_-]/g, '_')}_invalid`,
        cmd: `echo 'Invalid service name (only alphanumeric, hyphens, underscores allowed): ${name}'`,
        timeoutSec: 3,
      });
      continue;
    }
    out.push({
      id: `svc_${name}_status`,
      cmd: `systemctl status ${name} --no-pager | sed -n '1,40p'`,
      timeoutSec: 12,
    });
    out.push({
      id: `svc_${name}_errors`,
      cmd: `journalctl -u ${name} -p err..alert -n 80 --no-pager || true`,
      timeoutSec: 15,
    });
    out.push({
      id: `svc_${name}_recent`,
      cmd: `journalctl -u ${name} -n 120 --no-pager | egrep -i 'warn|warning|error|failed|fail|critical|crit|alert|panic|segfault|oom|killed process|timeout|timed out|refused|denied|unreachable|reset|broken pipe|i/o error|corrupt|read-only|no space|disk quota|throttl|backoff|rate limit|too many|conntrack|dropped' | tail -n 60 || true`,
      timeoutSec: 15,
    });
  }
  if (out.length === 0) {
    out.push({
      id: 'services_config',
      cmd: "echo 'No services configured for this target. Add targets.<name>.services in references/targets.yaml'",
      timeoutSec: 3,
    });
  }
  return out;
}

function buildDailyCommands(t) {
  const base = [
    { id: 'basic_identity', cmd: 'whoami; hostname; uname -r; date -Is', timeoutSec: 5 },
    { id: 'basic_uptime', cmd: 'uptime', timeoutSec: 5 },
    { id: 'basic_os', cmd: "cat /etc/os-release | sed -n '1,12p'", timeoutSec: 5 },
    { id: 'hw_cpu', cmd: "(command -v mpstat >/dev/null 2>&1 && mpstat -P ALL 1 3 | sed -n '1,160p') || (top -b -n1 | sed -n '1,25p') || true", timeoutSec: 15 },
    { id: 'hw_mem', cmd: "free -h; echo; cat /proc/meminfo | egrep -i '^(MemTotal|MemFree|MemAvailable|Buffers|Cached|SwapTotal|SwapFree|Dirty|Writeback|Slab):' || true", timeoutSec: 10 },
    { id: 'hw_disk_fs', cmd: "df -hT | sed -n '1,25p'", timeoutSec: 10 },
    { id: 'hw_disk_io', cmd: "(command -v iostat >/dev/null 2>&1 && iostat -x 1 3 | sed -n '1,120p') || true", timeoutSec: 18 },
    { id: 'hw_net_overview', cmd: "ss -s | sed -n '1,80p'", timeoutSec: 10 },
    { id: 'logs_journal_err_24h', cmd: 'journalctl -p err..alert -S -24h --no-pager | tail -n 200 || true', timeoutSec: 20 },
    { id: 'logs_dmesg_key', cmd: "dmesg -T 2>/dev/null | egrep -i 'error|fail|oom|killed process|segfault|panic|xfs|ext4|nvme|reset|link down|call trace' | tail -n 200 || true", timeoutSec: 12 },
    { id: 'sec_last_failed', cmd: "lastb -n 50 2>/dev/null | sed -n '1,60p' || true", timeoutSec: 12 },
    { id: 'sec_sshd_suspicious_24h', cmd: "journalctl -u sshd -S -24h --no-pager | egrep -i 'failed password|invalid user|authentication failure|maximum authentication attempts|POSSIBLE BREAK-IN ATTEMPT|Did not receive identification string|Connection closed by authenticating user|error: kex_exchange_identification' | tail -n 200 || true", timeoutSec: 20 },
    { id: 'systemd_failed_units', cmd: 'systemctl --failed --no-pager || true', timeoutSec: 10 },
    { id: 'systemd_recent_errors', cmd: 'journalctl -p err..alert -n 80 --no-pager || true', timeoutSec: 15 },
  ];
  const svc = buildServiceCommands(t?.services ?? []);
  return base.concat(svc);
}

function parseChecksYaml(text) {
  const lines = text.replace(/\r\n/g, '\n').split('\n');
  const out = { checks: {} };
  let curGroup = null;
  let inCommands = false;
  let curCmd = null;
  const kv = (s) => {
    const i = s.indexOf(':');
    if (i === -1) return null;
    return [s.slice(0, i).trim(), s.slice(i + 1).trim()];
  };
  for (let raw of lines) {
    const line = raw.replace(/\t/g, '  ');
    const t = line.trim();
    if (!t || t.startsWith('#')) continue;
    if (t === 'checks:') continue;
    if (/^[a-zA-Z0-9_-]+:$/.test(t) && line.startsWith('  ') && !line.startsWith('    ')) {
      curGroup = t.slice(0, -1);
      out.checks[curGroup] = { commands: [] };
      inCommands = false;
      curCmd = null;
      continue;
    }
    if (!curGroup) continue;
    if (t === 'commands:') { inCommands = true; curCmd = null; continue; }
    if (inCommands && t.startsWith('- ')) {
      curCmd = {};
      out.checks[curGroup].commands.push(curCmd);
      const rest = t.slice(2);
      const pair = kv(rest);
      if (pair) curCmd[pair[0]] = stripQuotes(pair[1]);
      continue;
    }
    const pair = kv(t);
    if (!pair) continue;
    if (!inCommands) {
      out.checks[curGroup][pair[0]] = stripQuotes(pair[1]);
    } else if (curCmd) {
      curCmd[pair[0]] = stripQuotes(pair[1]);
    }
  }
  return out;
}

function shellQuote(s) {
  return `'${String(s).replace(/'/g, `'"'"'`)}'`;
}

function renderReport({ target, host, user, checks, start, results, format = 'markdown' }) {
  if (format === 'json') {
    return JSON.stringify({
      target, host, user, checks, start,
      total: results.length,
      anomalies: results.filter(r => !r.ok || hasAnomaly(r.stdout, r.stderr)).length,
      results,
    }, null, 2);
  }

  const errorItems = results.filter(r => !r.ok || hasAnomaly(r.stdout, r.stderr));
  let md = '';
  md += `# 🔍 Server Inspection Report\n\n`;
  md += `- Target: \`${mdEscape(target)}\`\n`;
  md += `- Host: \`${mdEscape(host)}\`\n`;
  md += `- User: \`${mdEscape(user)}\`\n`;
  md += `- Checks: \`${mdEscape(checks)}\`\n`;
  md += `- Started: \`${mdEscape(start)}\`\n`;
  md += `- Total checks: ${results.length}\n`;
  md += `- ⚠️ Anomalies: ${errorItems.length}\n\n`;

  // Summary
  const status = errorItems.length === 0 ? '✅ HEALTHY' : errorItems.length <= 3 ? '⚠️ WARNING' : '🚨 CRITICAL';
  md += `## Overall Status: ${status}\n\n`;

  // Anomaly section (priority)
  if (errorItems.length > 0) {
    md += `## ⚠️ Anomalies (Priority)\n\n`;
    for (const r of errorItems) {
      md += `### ${r.ok ? '⚠️' : '❌'} ${mdEscape(r.id)}\n\n`;
      md += `Command: \`${mdEscape(r.cmd)}\`\n\n`;
      md += `Status: ${r.ok ? 'OK (contains anomalies)' : 'FAIL'} (timeout ${r.timeoutSec}s)\n\n`;
      if (r.stdout.trim()) {
        md += `Output:\n\n\`\`\`\n${r.stdout.trim()}\n\`\`\`\n\n`;
      }
      if (r.stderr.trim()) {
        md += `Stderr:\n\n\`\`\`\n${r.stderr.trim()}\n\`\`\`\n\n`;
      }
    }
  }

  // Normal section (collapsible)
  md += `<details><summary>📋 View all check results (${results.length} total)</summary>\n\n`;
  for (const r of results.filter(r => !errorItems.includes(r))) {
    md += `### ✅ ${mdEscape(r.id)}\n\n`;
    md += `Command: \`${mdEscape(r.cmd)}\`\n\n`;
    md += `Status: OK (timeout ${r.timeoutSec}s)\n\n`;
    if (r.stdout.trim()) {
      md += `Output:\n\n\`\`\`\n${r.stdout.trim()}\n\`\`\`\n\n`;
    }
  }
  md += `</details>\n`;
  return md;
}

async function main() {
  const args = parseArgs(process.argv);
  if (args.help || !args.target) {
    usage();
    if (!args.target) process.exitCode = 2;
    return;
  }

  const here = dirname(fileURLToPath(import.meta.url));
  const skillDir = dirname(here);
  const targetsPath = join(skillDir, 'references', 'targets.yaml');
  const checksPath = join(skillDir, 'references', 'checks.yaml');

  // SECURITY VALIDATION: Ensure we only access allowed files
  // This prevents the script from being used to read arbitrary files
  const allowedPaths = [targetsPath, checksPath];
  for (const p of allowedPaths) {
    try {
      await readFile(p, 'utf-8');
    } catch (e) {
      console.error(`Error: Required file not found: ${p}`);
      process.exitCode = 1;
      return;
    }
  }

  const targetsText = await readFile(targetsPath, 'utf-8');
  const checksText = await readFile(checksPath, 'utf-8');

  const targets = parseSimpleYaml(targetsText);
  const t = targets.targets?.[args.target];
  if (!t) throw new Error(`Unknown target: ${args.target}`);

  const checks = parseChecksYaml(checksText);
  const group = checks.checks?.[args.checks];
  if (!group) throw new Error(`Unknown checks group: ${args.checks}`);

  // Dynamic command generation
  if (args.checks === 'services') {
    group.commands = buildServiceCommands(t.services ?? []);
  }
  if (args.checks === 'daily') {
    group.commands = buildDailyCommands(t);
  }

  const sshBase = [
    '-i', String(t.keyPath).replace('~', process.env.HOME || '/root'),
    '-p', String(t.port ?? 22),
    '-o', 'BatchMode=yes',
    '-o', 'StrictHostKeyChecking=accept-new',
    '-o', 'ConnectTimeout=8',
  ];
  const dest = `${t.user}@${t.host}`;
  const start = nowIso();
  const results = [];

  for (const c of group.commands) {
    const timeoutMs = Number(c.timeoutSec ?? 10) * 1000;
    const remote = `bash -lc ${shellQuote(c.cmd)}`;
    const { error, stdout, stderr } = await execFileP('ssh', [...sshBase, dest, remote], { timeoutMs });
    results.push({
      id: c.id, cmd: c.cmd, timeoutSec: c.timeoutSec ?? 10,
      ok: !error, code: error?.code ?? 0, stdout, stderr,
    });
  }

  const report = renderReport({
    target: args.target, host: t.host, user: t.user,
    checks: args.checks, start, results, format: args.format,
  });

  if (args.output) {
    const { writeFile } = await import('node:fs/promises');
    await writeFile(args.output, report);
    console.error(`Report written to: ${args.output}`);
  } else {
    try { process.stdout.write(report); }
    catch (e) { if (e?.code !== 'EPIPE') throw e; }
  }
}

main().catch((err) => {
  console.error(err?.stack || String(err));
  process.exitCode = 1;
});


path: scripts/inspect.py
content: #!/usr/bin/env python3
"""
li_sentry_check - Multi-platform server inspection (Python version)
- Loads targets from references/targets.yaml
- Loads allowlisted checks from references/checks.yaml
- Runs each command over SSH (non-interactive), captures stdout/stderr
- Prints a Markdown/JSON report with anomaly highlighting
Compatible with NanoBot and Hermes agent.

SECURITY CONSTRAINTS:
- ONLY reads from: references/targets.yaml, references/checks.yaml, SSH key
- ONLY connects to ONE server via SSH (target specified in targets.yaml)
- ONLY executes commands from references/checks.yaml allowlist
- NEVER modifies server state, installs software, or writes files
- NEVER exfiltrates data to external services
- NEVER executes arbitrary commands
"""
import argparse
import json
import os
import re
import subprocess
import sys
from datetime import datetime, timezone
from pathlib import Path


# SECURITY: Only these files are read
ALLOWED_FILES = [
    "references/targets.yaml",
    "references/checks.yaml",
]

# SECURITY: Only SSH connections are made (no HTTP, no external APIs)
# SECURITY: Only commands from checks.yaml are executed
# SECURITY: No state changes on remote servers (read-only)


# Error keywords for anomaly detection
ERROR_KEYWORDS = [
    "failed", "error", "alert", "critical", "SELinux is preventing",
    "WARNING", "panic", "segfault", "oom", "killed process",
    "no space", "disk quota", "read-only", "corrupt", "timeout",
    "refused", "denied", "unreachable", "broken pipe", "i/o error",
]


def parse_simple_yaml(text: str) -> dict:
    """Simple YAML parser (no external dependencies)."""
    lines = text.replace("\r\n", "\n").split("\n")
    root = {}
    stack = [{"indent": -1, "obj": root}]

    for idx, raw in enumerate(lines):
        line = raw.replace("\t", "  ")
        if not line.strip() or line.strip().startswith("#"):
            continue

        indent = len(line) - len(line.lstrip())
        while stack and indent <= stack[-1]["indent"]:
            stack.pop()

        parent = stack[-1]["obj"]
        trimmed = line.strip()

        if trimmed.startswith("- "):
            if not isinstance(parent, list):
                raise ValueError("YAML list item in non-list")
            parent.append(_strip_quotes(trimmed[2:].strip()))
            continue

        if ":" not in trimmed:
            continue

        key, _, value = trimmed.partition(":")
        key = key.strip()
        value = value.strip()

        if value == "":
            # Look ahead to determine if this is a list or dict
            next_indent = None
            next_trimmed = None
            for j in range(idx + 1, len(lines)):
                nl = lines[j].replace("\t", "  ")
                nt = nl.strip()
                if nt and not nt.startswith("#"):
                    next_indent = len(nl) - len(nl.lstrip())
                    next_trimmed = nt
                    break

            is_list = (next_indent is not None and
                       next_indent > indent and
                       next_trimmed.startswith("- "))
            container = [] if is_list else {}
            parent[key] = container
            stack.append({"indent": indent, "obj": container})
        else:
            parent[key] = _strip_quotes(value)

    return root


def _strip_quotes(s: str):
    """Remove surrounding quotes from a string."""
    if len(s) >= 2 and s[0] == s[-1] and s[0] in ('"', "'"):
        return s[1:-1]
    if re.match(r"^\d+$", s):
        return int(s)
    return s


def parse_checks_yaml(text: str) -> dict:
    """Parse checks.yaml with special handling for command lists."""
    lines = text.replace("\r\n", "\n").split("\n")
    out = {"checks": {}}
    cur_group = None
    in_commands = False
    cur_cmd = None

    for raw in lines:
        line = raw.replace("\t", "  ")
        t = line.strip()
        if not t or t.startswith("#"):
            continue
        if t == "checks:":
            continue

        # Top-level group (2-space indent, ends with :)
        if re.match(r"^[a-zA-Z0-9_-]+:$", t) and line.startswith("  ") and not line.startswith("    "):
            cur_group = t[:-1]
            out["checks"][cur_group] = {"commands": []}
            in_commands = False
            cur_cmd = None
            continue

        if not cur_group:
            continue

        if t == "commands:":
            in_commands = True
            cur_cmd = None
            continue

        if in_commands and t.startswith("- "):
            cur_cmd = {}
            out["checks"][cur_group]["commands"].append(cur_cmd)
            rest = t[2:]
            if ":" in rest:
                k, _, v = rest.partition(":")
                cur_cmd[k.strip()] = _strip_quotes(v.strip())
            continue

        if ":" in t:
            k, _, v = t.partition(":")
            k = k.strip()
            v = v.strip()
            if not in_commands:
                out["checks"][cur_group][k] = _strip_quotes(v)
            elif cur_cmd is not None:
                cur_cmd[k] = _strip_quotes(v)

    return out


def build_service_commands(services: list) -> list:
    """Dynamically generate service inspection commands."""
    out = []
    uniq = list(dict.fromkeys(s.strip() for s in services if s.strip()))

    for name in uniq:
        # Validate service name to prevent command injection
        if not re.match(r'^[a-zA-Z0-9_-]+$', name):
            safe_name = re.sub(r'[^a-zA-Z0-9_-]', '_', name)
            out.append({
                "id": f"svc_{safe_name}_invalid",
                "cmd": f"echo 'Invalid service name (only alphanumeric, hyphens, underscores allowed): {name}'",
                "timeoutSec": 3,
            })
            continue

        out.append({
            "id": f"svc_{name}_status",
            "cmd": f"systemctl status {name} --no-pager | sed -n '1,40p'",
            "timeoutSec": 12,
        })
        out.append({
            "id": f"svc_{name}_errors",
            "cmd": f"journalctl -u {name} -p err..alert -n 80 --no-pager || true",
            "timeoutSec": 15,
        })
        out.append({
            "id": f"svc_{name}_recent",
            "cmd": (
                f"journalctl -u {name} -n 120 --no-pager | "
                f"egrep -i 'warn|warning|error|failed|fail|critical|crit|alert|panic|segfault|oom|"
                f"killed process|timeout|timed out|refused|denied|unreachable|reset|broken pipe|"
                f"i/o error|corrupt|read-only|no space|disk quota|throttl|backoff|rate limit|"
                f"too many|conntrack|dropped' | tail -n 60 || true"
            ),
            "timeoutSec": 15,
        })

    if not out:
        out.append({
            "id": "services_config",
            "cmd": "echo 'No services configured for this target. Add targets.<name>.services in references/targets.yaml'",
            "timeoutSec": 3,
        })

    return out


def build_daily_commands(target: dict) -> list:
    """Build full daily inspection commands."""
    base = [
        {"id": "basic_identity", "cmd": "whoami; hostname; uname -r; date -Is", "timeoutSec": 5},
        {"id": "basic_uptime", "cmd": "uptime", "timeoutSec": 5},
        {"id": "basic_os", "cmd": "cat /etc/os-release | sed -n '1,12p'", "timeoutSec": 5},
        {"id": "hw_cpu", "cmd": "(command -v mpstat >/dev/null 2>&1 && mpstat -P ALL 1 3 | sed -n '1,160p') || (top -b -n1 | sed -n '1,25p') || true", "timeoutSec": 15},
        {"id": "hw_mem", "cmd": "free -h; echo; cat /proc/meminfo | egrep -i '^(MemTotal|MemFree|MemAvailable|Buffers|Cached|SwapTotal|SwapFree|Dirty|Writeback|Slab):' || true", "timeoutSec": 10},
        {"id": "hw_disk_fs", "cmd": "df -hT | sed -n '1,25p'", "timeoutSec": 10},
        {"id": "hw_disk_io", "cmd": "(command -v iostat >/dev/null 2>&1 && iostat -x 1 3 | sed -n '1,120p') || true", "timeoutSec": 18},
        {"id": "hw_net_overview", "cmd": "ss -s | sed -n '1,80p'", "timeoutSec": 10},
        {"id": "logs_journal_err_24h", "cmd": "journalctl -p err..alert -S -24h --no-pager | tail -n 200 || true", "timeoutSec": 20},
        {"id": "logs_dmesg_key", "cmd": "dmesg -T 2>/dev/null | egrep -i 'error|fail|oom|killed process|segfault|panic|xfs|ext4|nvme|reset|link down|call trace' | tail -n 200 || true", "timeoutSec": 12},
        {"id": "sec_last_failed", "cmd": "lastb -n 50 2>/dev/null | sed -n '1,60p' || true", "timeoutSec": 12},
        {"id": "sec_sshd_suspicious_24h", "cmd": "journalctl -u sshd -S -24h --no-pager | egrep -i 'failed password|invalid user|authentication failure|maximum authentication attempts|POSSIBLE BREAK-IN ATTEMPT|Did not receive identification string|Connection closed by authenticating user|error: kex_exchange_identification' | tail -n 200 || true", "timeoutSec": 20},
        {"id": "systemd_failed_units", "cmd": "systemctl --failed --no-pager || true", "timeoutSec": 10},
        {"id": "systemd_recent_errors", "cmd": "journalctl -p err..alert -n 80 --no-pager || true", "timeoutSec": 15},
    ]
    svc = build_service_commands(target.get("services", []))
    return base + svc


def expand_path(path: str) -> str:
    """Expand ~ and environment variables in path."""
    return os.path.expanduser(os.path.expandvars(path))


def run_ssh_command(ssh_base: list, dest: str, cmd: str, timeout_sec: int) -> dict:
    """Run a command on remote server via SSH.
    
    SECURITY: This function ONLY executes SSH commands.
    - No HTTP requests
    - No file writes to remote server
    - No local file access beyond what's specified
    """
    remote = f"bash -lc '{cmd.replace(chr(39), chr(39) + '\"' + chr(39) + chr(39))}'"
    full_cmd = ssh_base + [dest, remote]

    try:
        result = subprocess.run(
            full_cmd,
            capture_output=True,
            text=True,
            timeout=timeout_sec,
            max_buffer_size=10 * 1024 * 1024,
        )
        return {
            "stdout": result.stdout.strip(),
            "stderr": result.stderr.strip(),
            "ok": result.returncode == 0,
            "code": result.returncode,
        }
    except subprocess.TimeoutExpired:
        return {
            "stdout": "",
            "stderr": f"Command timed out after {timeout_sec}s",
            "ok": False,
            "code": -1,
        }
    except FileNotFoundError:
        return {
            "stdout": "",
            "stderr": "SSH command not found. Is openssh-client installed?",
            "ok": False,
            "code": -1,
        }
    except Exception as e:
        return {
            "stdout": "",
            "stderr": str(e),
            "ok": False,
            "code": -1,
        }


def has_anomaly(stdout: str, stderr: str) -> bool:
    """Check if output contains any anomaly keywords."""
    combined = (stdout + stderr).lower()
    return any(kw.lower() in combined for kw in ERROR_KEYWORDS)


def md_escape(s: str) -> str:
    """Escape backticks for Markdown."""
    return s.replace("`", "\\`")


def render_report(target: str, host: str, user: str, checks: str,
                  start: str, results: list, fmt: str = "markdown") -> str:
    """Generate inspection report in Markdown or JSON format."""
    if fmt == "json":
        anomaly_count = sum(1 for r in results if not r["ok"] or has_anomaly(r["stdout"], r["stderr"]))
        return json.dumps({
            "target": target,
            "host": host,
            "user": user,
            "checks": checks,
            "start": start,
            "total": len(results),
            "anomalies": anomaly_count,
            "results": results,
        }, indent=2)

    error_items = [r for r in results if not r["ok"] or has_anomaly(r["stdout"], r["stderr"])]

    md = ""
    md += "# 🔍 Server Inspection Report\n\n"
    md += f"- Target: `{md_escape(target)}`\n"
    md += f"- Host: `{md_escape(host)}`\n"
    md += f"- User: `{md_escape(user)}`\n"
    md += f"- Checks: `{md_escape(checks)}`\n"
    md += f"- Started: `{md_escape(start)}`\n"
    md += f"- Total checks: {len(results)}\n"
    md += f"- ⚠️ Anomalies: {len(error_items)}\n\n"

    # Overall status
    if len(error_items) == 0:
        status = "✅ HEALTHY"
    elif len(error_items) <= 3:
        status = "⚠️ WARNING"
    else:
        status = "🚨 CRITICAL"
    md += f"## Overall Status: {status}\n\n"

    # Anomaly section (priority)
    if error_items:
        md += "## ⚠️ Anomalies (Priority)\n\n"
        for r in error_items:
            icon = "⚠️" if r["ok"] else "❌"
            status_text = "OK (contains anomalies)" if r["ok"] else "FAIL"
            md += f"### {icon} {md_escape(r['id'])}\n\n"
            md += f"Command: `{md_escape(r['cmd'])}`\n\n"
            md += f"Status: {status_text} (timeout {r['timeoutSec']}s)\n\n"
            if r["stdout"].strip():
                md += f"Output:\n\n```\n{r['stdout'].strip()}\n```\n\n"
            if r["stderr"].strip():
                md += f"Stderr:\n\n```\n{r['stderr'].strip()}\n```\n\n"

    # Normal section (collapsible)
    md += "<details><summary>📋 View all check results"
    md += f" ({len(results)} total)</summary>\n\n"
    for r in results:
        if r not in error_items:
            md += f"### ✅ {md_escape(r['id'])}\n\n"
            md += f"Command: `{md_escape(r['cmd'])}`\n\n"
            md += f"Status: OK (timeout {r['timeoutSec']}s)\n\n"
            if r["stdout"].strip():
                md += f"Output:\n\n```\n{r['stdout'].strip()}\n```\n\n"
    md += "</details>\n"

    return md


def main():
    parser = argparse.ArgumentParser(description="li_sentry_check - Server inspection tool")
    parser.add_argument("--target", required=True, help="Target name from targets.yaml")
    parser.add_argument("--checks", default="basic", help="Check group: basic, services, daily")
    parser.add_argument("--format", choices=["markdown", "json"], default="markdown", help="Output format")
    parser.add_argument("--output", help="Write report to file")
    args = parser.parse_args()

    # Resolve paths relative to this script
    script_dir = Path(__file__).resolve().parent
    skill_dir = script_dir.parent
    targets_path = skill_dir / "references" / "targets.yaml"
    checks_path = skill_dir / "references" / "checks.yaml"

    # SECURITY VALIDATION: Ensure we only access allowed files
    # This prevents the script from being used to read arbitrary files
    allowed_paths = [
        str(targets_path.resolve()),
        str(checks_path.resolve()),
    ]
    for p in allowed_paths:
        if not Path(p).exists():
            print(f"Error: Required file not found: {p}", file=sys.stderr)
            sys.exit(1)

    # Read and parse config files
    targets_text = targets_path.read_text(encoding="utf-8")
    checks_text = checks_path.read_text(encoding="utf-8")

    targets = parse_simple_yaml(targets_text)
    target = targets.get("targets", {}).get(args.target)
    if not target:
        print(f"Error: Unknown target: {args.target}", file=sys.stderr)
        sys.exit(1)

    checks = parse_checks_yaml(checks_text)
    group = checks.get("checks", {}).get(args.checks)
    if not group:
        print(f"Error: Unknown checks group: {args.checks}", file=sys.stderr)
        sys.exit(1)

    # Dynamic command generation
    commands = group.get("commands", [])
    if args.checks == "services":
        commands = build_service_commands(target.get("services", []))
    elif args.checks == "daily":
        commands = build_daily_commands(target)

    # SSH connection parameters
    key_path = expand_path(str(target.get("keyPath", "~/.ssh/li_sentry_check")))
    port = str(target.get("port", 22))
    user = str(target["user"])
    host = str(target["host"])

    ssh_base = [
        "ssh",
        "-i", key_path,
        "-p", port,
        "-o", "BatchMode=yes",
        "-o", "StrictHostKeyChecking=accept-new",
        "-o", "ConnectTimeout=8",
    ]
    dest = f"{user}@{host}"

    start = datetime.now(timezone.utc).isoformat()
    results = []

    # Execute inspection commands
    for cmd in commands:
        cmd_id = cmd.get("id", "unknown")
        cmd_str = cmd.get("cmd", "echo 'No command'")
        timeout = int(cmd.get("timeoutSec", 10))

        result = run_ssh_command(ssh_base, dest, cmd_str, timeout)
        results.append({
            "id": cmd_id,
            "cmd": cmd_str,
            "timeoutSec": timeout,
            "ok": result["ok"],
            "code": result["code"],
            "stdout": result["stdout"],
            "stderr": result["stderr"],
        })

    # Generate report
    report = render_report(args.target, host, user, args.checks, start, results, args.format)

    if args.output:
        Path(args.output).write_text(report, encoding="utf-8")
        print(f"Report written to: {args.output}", file=sys.stderr)
    else:
        print(report)


if __name__ == "__main__":
    main()


Security scan verdicts — ClawScan: clean | static analysis: suspicious | VirusTotal: clean | SkillSpector: suspicious (CRITICAL)