Skill: pfrederiksen/openclaw-update-checker (version 1.1.1)

--- name: openclaw-update-checker description: "Check for OpenClaw updates by comparing installed version against the npm registry. Use when: user asks about updates, version status, or 'is openclaw up to date'. Also useful in heartbeats/cron for periodic update monitoring. Read-only — reports status only, does not modify the system." --- # OpenClaw Update Checker Read-only version checker. Compares the installed OpenClaw version against the npm registry and reports whether updates are available. Does not install, modify, or restart anything. ## Usage ```bash # Human-readable output python3 scripts/check_update.py # Machine-readable JSON (for dashboards, cron, integrations) python3 scripts/check_update.py --format json ``` ## Output **Text mode:** One-liner if current, or a summary showing installed vs latest version and number of versions behind. **JSON mode:** ```json { "installed": "2026.2.21-2", "latest": "2026.2.21-2", "up_to_date": true, "newer_versions": [], "changelog_url": "https://github.com/openclaw/openclaw/releases/tag/v2026.2.21" } ``` ## How It Works 1. **Reads the installed version** from the local `package.json` file at known npm global install paths (`/usr/lib/node_modules/openclaw/package.json` or `/usr/local/lib/node_modules/openclaw/package.json`) 2. **Queries the npm registry** via an HTTPS GET request to `https://registry.npmjs.org/openclaw` using Python's `urllib.request` (stdlib) 3. **Compares versions** and reports the result ## What It Does NOT Do - Does not install or update any packages - Does not write to any files - Does not restart any services - Does not execute any subprocesses or shell commands ## System Access - **File reads:** `/usr/lib/node_modules/openclaw/package.json` and `/usr/local/lib/node_modules/openclaw/package.json` (read-only, to determine installed version) - **Network:** Single HTTPS GET to `https://registry.npmjs.org/openclaw` (read-only, no authentication, to fetch available versions) ## Requirements - Python 3.10+ (uses `str | None` type union syntax) - OpenClaw installed globally via npm - Outbound HTTPS access to `registry.npmjs.org`

path: scripts/check_update.py
content: #!/usr/bin/env python3
"""
OpenClaw Update Checker

Compares the locally installed openclaw version against the npm registry
to determine if updates are available.

Read-only — no files are modified, no packages installed, no services restarted.
No subprocess calls — reads local package.json and queries the npm registry
API via HTTPS directly.

Usage:
    python3 check_update.py              # Human-readable text output
    python3 check_update.py --format json # Machine-readable JSON output
"""

import json
import re
import sys
import urllib.request
import urllib.error
from pathlib import Path


# Known install locations for openclaw's package.json
_PACKAGE_JSON_PATHS = [
    Path("/usr/lib/node_modules/openclaw/package.json"),
    Path("/usr/local/lib/node_modules/openclaw/package.json"),
]

# npm public registry endpoint (read-only, no auth required)
_NPM_REGISTRY_URL = "https://registry.npmjs.org/openclaw"


def get_installed_version() -> str | None:
    """Read the installed openclaw version from its package.json."""
    for p in _PACKAGE_JSON_PATHS:
        try:
            data = json.loads(p.read_text(encoding="utf-8"))
            version = data.get("version")
            if version:
                return str(version)
        except (FileNotFoundError, PermissionError, json.JSONDecodeError):
            continue
    return None


def get_registry_versions() -> list[str] | None:
    """Fetch all published versions from the npm registry via HTTPS."""
    try:
        req = urllib.request.Request(
            _NPM_REGISTRY_URL,
            headers={"Accept": "application/json"},
        )
        with urllib.request.urlopen(req, timeout=15) as resp:
            data = json.loads(resp.read().decode("utf-8"))
            versions = list(data.get("versions", {}).keys())
            return versions if versions else None
    except (urllib.error.URLError, json.JSONDecodeError, OSError):
        return None


def parse_version(v: str) -> tuple[int, int, int, int]:
    """Parse YYYY.M.DD[-N] version string into a comparable tuple."""
    match = re.match(r"(\d+)\.(\d+)\.(\d+)(?:-(\d+))?", v)
    if not match:
        return (0, 0, 0, 0)
    return (
        int(match.group(1)),
        int(match.group(2)),
        int(match.group(3)),
        int(match.group(4)) if match.group(4) else 0,
    )


def get_latest_version(versions: list[str]) -> str | None:
    """Return the highest version from a list."""
    if not versions:
        return None
    return max(versions, key=parse_version)


def get_changelog_url(version: str) -> str:
    """Generate the GitHub release URL for a given version."""
    base = re.sub(r"-\d+$", "", version)
    return f"https://github.com/openclaw/openclaw/releases/tag/v{base}"


def main() -> None:
    output_format = "text"
    if "--format" in sys.argv:
        idx = sys.argv.index("--format")
        if idx + 1 < len(sys.argv):
            output_format = sys.argv[idx + 1]

    installed = get_installed_version()
    if not installed:
        msg = "Could not determine installed openclaw version"
        print(json.dumps({"error": msg}) if output_format == "json" else f"Error: {msg}")
        sys.exit(1)

    versions = get_registry_versions()
    if not versions:
        msg = "Could not fetch versions from npm registry"
        print(json.dumps({"error": msg}) if output_format == "json" else f"Error: {msg}")
        sys.exit(1)

    latest = get_latest_version(versions)
    installed_tuple = parse_version(installed)
    is_current = installed_tuple >= parse_version(latest)

    newer = sorted(
        [v for v in versions if parse_version(v) > installed_tuple],
        key=parse_version,
    )

    result = {
        "installed": installed,
        "latest": latest,
        "up_to_date": is_current,
        "newer_versions": newer,
        "changelog_url": get_changelog_url(latest),
    }

    if output_format == "json":
        print(json.dumps(result, indent=2))
    elif is_current:
        print(f"✅ OpenClaw is up to date: {installed}")
    else:
        print(f"⬆️  Update available: {installed} → {latest}")
        print(f"   Versions behind: {len(newer)}")
        shown = newer if len(newer) <= 5 else newer[-5:]
        for v in shown:
            print(f"   • {v}")
        if len(newer) > 5:
            print(f"   ... and {len(newer) - 5} more")
        print(f"   Release: {get_changelog_url(latest)}")


if __name__ == "__main__":
    main()


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