Back to skills
SkillHub ClubResearch & OpsFull StackDevOps

openclaw-ops-elvatis

Operational commands - dashboards, monitoring, and management for OpenClaw deployments.

Packaged view

This page reorganizes the original catalog entry around fit, installability, and workflow context first. The original raw source lives below.

Stars
3,114
Hot score
99
Updated
March 20, 2026
Overall rating
C4.0
Composite score
4.0
Best-practice grade
B73.2

Install command

npx @skill-hub/cli install openclaw-skills-openclaw-ops-elvatis

Repository

openclaw/skills

Skill path: skills/homeofe/openclaw-ops-elvatis

Operational commands - dashboards, monitoring, and management for OpenClaw deployments.

Open repository

Best for

Primary workflow: Research & Ops.

Technical facets: Full Stack, DevOps.

Target audience: everyone.

License: Unknown.

Original source

Catalog source: SkillHub Club.

Repository owner: openclaw.

This is still a mirrored public skill entry. Review the repository before installing into production workflows.

What it helps with

  • Install openclaw-ops-elvatis into Claude Code, Codex CLI, Gemini CLI, or OpenCode workflows
  • Review https://github.com/openclaw/skills before adding openclaw-ops-elvatis to shared team environments
  • Use openclaw-ops-elvatis for development workflows

Works across

Claude CodeCodex CLIGemini CLIOpenCode

Favorites: 0.

Sub-skills: 0.

Aggregator: No.

Original source / Raw SKILL.md

---
name: openclaw-ops-elvatis
description: Operational commands - dashboards, monitoring, and management for OpenClaw deployments.
---

# OpenClaw Ops (Elvatis)

Operational tooling for OpenClaw deployments: dashboards, monitoring, config validation, safe restarts, and deployment management.

## Extension Commands

### Config Commands (`config-commands.ts`)
Validate and inspect OpenClaw configuration files. Ensures valid JSON, detects legacy keys, and guards against broken configs reaching production.

### Observer Commands (`observer-commands.ts`)
Monitor gateway health, track plugin status, and surface operational metrics for running OpenClaw instances.

### Phase 1 Commands (`phase1-commands.ts`)
Core operational commands for day-to-day management: status checks, restart orchestration, and deployment health verification.

### Skills Commands (`skills-commands.ts`)
Manage installed skills: list, inspect, and verify skill integrity across the workspace.

### Legacy Commands (`legacy-commands.ts`)
Backward-compatible command support for older OpenClaw configurations during migration periods.

## Scripts

### Config Validation (`openclaw-config-validate.py`)
Validates `openclaw.json` against known schema rules — checks for valid JSON, forbidden legacy keys (e.g. `plugins.paths`), and verifies a golden master config exists.

### Safe Restart (`openclaw-safe-restart.sh`)
Backup → validate → restart → restore-on-failure workflow. Prevents broken configs from taking down the gateway.

### GitHub Privacy Scanning
Automated scanning of repos for accidentally committed private files (MEMORY.md, TODO.md, HEARTBEAT.md, .env). Runs via daily cron.

### Issue Triage (`triage_labels.py`)
Automated GitHub issue labeling and triage for openclaw-* repositories.

## Installation

Install via ClawHub:
```bash
clawhub install openclaw-ops-elvatis
```


---

## Skill Companion Files

> Additional files collected from the skill directory layout.

### README.md

```markdown
# openclaw-ops-elvatis

Local ops plugin.

## Commands

### Operations & Monitoring (Phase 1)
- `/health` - Quick system health check (gateway, resources, plugins, errors)
- `/services` - Show all OpenClaw profiles and service status
- `/logs [service] [lines]` - View gateway or plugin logs (defaults: gateway, 50 lines)
- `/plugins` - Detailed plugin dashboard with versions and workspace info

### Configuration (Phase 2)
- `/config` - Show configuration overview (environment, main config, plugin configs, env vars)
- `/config <plugin>` - Show detailed config for a specific plugin (values, schema validation, defaults comparison)

### Legacy Commands
- `/cron` - list cron jobs + scripts + recent reports
- `/privacy-scan` - run the GitHub privacy scan and show latest report path
- `/limits` - show provider auth expiry + observed cooldown windows
- `/release` - show staging gateway + human GO checklist (QA gate)
- `/staging-smoke` - install all `openclaw-*` plugins into the staging profile, restart gateway, and verify status (writes report)
- `/handoff` - show latest openclaw-ops-elvatis handoff log tail

## Usage Examples

### Quick Health Check
```bash
openclaw health
```
Shows gateway status, system resources (CPU, memory, disk), plugin count, and recent errors.

### View Logs
```bash
# View last 50 lines of gateway logs (default)
openclaw logs

# View last 100 lines of specific plugin
openclaw logs openclaw-ops-elvatis 100

# View audit logs
openclaw logs audit 200
```

### Service Management
```bash
# Check all profiles
openclaw services

# View detailed plugin info
openclaw plugins
```

### Operations Dashboard
```bash
# Full operational overview
openclaw cron          # Scheduled tasks
openclaw limits        # Rate limits and auth expiry
openclaw health        # System health
openclaw services      # All services
```

## GitHub Actions

### openclaw-triage-labels

Automated issue labeling across all `elvatis/openclaw-*` repositories. Runs daily at 06:00 UTC and can be triggered manually via `workflow_dispatch`.

Applies labels based on keyword analysis:
- `security` - security-related issues (CVE, vulnerability, XSS, etc.)
- `bug` - bug reports (crash, error, regression, etc.)
- `needs-triage` - everything else that has not been triaged yet

#### Cross-repo PAT setup (required for multi-repo triage)

The default `GITHUB_TOKEN` is scoped to `openclaw-ops-elvatis` only. To label issues in sibling repos (`openclaw-memory-core`, `openclaw-gpu-bridge`, etc.), you must configure a fine-grained Personal Access Token:

1. Go to [GitHub token settings](https://github.com/settings/tokens?type=beta)
2. Create a fine-grained PAT with:
   - **Repository access**: All repositories (or select all `elvatis/openclaw-*` repos)
   - **Permissions**: Issues (Read and write), Metadata (Read)
3. Go to this repo's **Settings > Secrets and variables > Actions**
4. Add a repository secret named `TRIAGE_GH_TOKEN` with the PAT value

Without `TRIAGE_GH_TOKEN`, the workflow gracefully skips repos where it lacks permissions (no 403 errors - just a warning in the logs).

## Install

```bash
openclaw plugins install -l ~/.openclaw/workspace/openclaw-ops-elvatis
openclaw gateway restart
```

```

### _meta.json

```json
{
  "owner": "homeofe",
  "slug": "openclaw-ops-elvatis",
  "displayName": "OpenClaw Ops",
  "latest": {
    "version": "0.2.1",
    "publishedAt": 1772282364538,
    "commit": "https://github.com/openclaw/skills/commit/71f3a40637ab61a672143372fbe23c99ad5d7b4c"
  },
  "history": []
}

```

### scripts/triage_labels.py

```python
#!/usr/bin/env python3
"""Labeling-only GitHub issue triage across repos.

Safety properties:
- No commits, no branches, no PRs.
- Only reads repos/issues and adds labels to issues.
- Does NOT remove existing labels.

Env:
  GITHUB_TOKEN      Default token in GitHub Actions (preferred)
  TRIAGE_GH_TOKEN   Optional override token (fine-grained PAT) if you want explicit token control
  GH_OWNER          Org/user to scan (default: elvatis)
  REPO_PREFIX       Repo name prefix filter (default: openclaw-)
  PER_REPO_LIMIT    Max open issues per repo to consider (default: 30)
  SKIP_ARCHIVED     "1" to skip archived repos
  SKIP_FORKS        "1" to skip forks
"""

from __future__ import annotations

import os
import re
import sys
from dataclasses import dataclass
from typing import Any, Dict, Iterable, List, Optional, Tuple

import requests

API = "https://api.github.com"

SECURITY_KEYWORDS = [
    "security",
    "cve",
    "vuln",
    "vulnerability",
    "xss",
    "ssrf",
    "csrf",
    "rce",
    "auth bypass",
    "token leak",
]

BUG_KEYWORDS = [
    "bug",
    "crash",
    "panic",
    "exception",
    "error",
    "failing",
    "test fails",
    "regression",
    "broken",
]

LABELS = {
    "security": {"color": "b60205", "description": "Security-related issue"},
    "bug": {"color": "d73a4a", "description": "Something isn't working"},
    "needs-triage": {"color": "ededed", "description": "Needs initial triage"},
}


@dataclass
class Counts:
    security: int = 0
    bug: int = 0
    needs_triage: int = 0


def env(name: str, default: Optional[str] = None) -> str:
    v = os.environ.get(name)
    if v is None or v == "":
        if default is None:
            raise SystemExit(f"Missing env var: {name}")
        return default
    return v


def bool_env(name: str, default: bool = False) -> bool:
    v = os.environ.get(name)
    if v is None:
        return default
    return v.strip() not in ("0", "false", "False", "no", "NO", "")


def session(token: str) -> requests.Session:
    s = requests.Session()
    s.headers.update(
        {
            "Authorization": f"Bearer {token}",
            "Accept": "application/vnd.github+json",
            "X-GitHub-Api-Version": "2022-11-28",
            "User-Agent": "openclaw-triage-labels",
        }
    )
    return s


class GitHubPermissionError(RuntimeError):
    """Raised when the token lacks permission for a GitHub API call (403)."""


class GitHubNotFoundError(RuntimeError):
    """Raised when a GitHub API endpoint returns 404."""


def gh_get_paginated(s: requests.Session, url: str, params: Dict[str, Any] | None = None) -> Iterable[Dict[str, Any]]:
    """Yield items from a GitHub API endpoint that returns an array.

    Raises GitHubPermissionError on 403 and GitHubNotFoundError on 404
    so callers can handle them distinctly.
    """
    while url:
        r = s.get(url, params=params)
        params = None  # only for first page
        if r.status_code == 403:
            raise GitHubPermissionError(f"GET {url} returned 403: {r.text[:200]}")
        if r.status_code == 404:
            raise GitHubNotFoundError(f"GET {url} returned 404: {r.text[:200]}")
        if r.status_code >= 400:
            raise RuntimeError(f"GET {url} failed: {r.status_code} {r.text[:200]}")
        data = r.json()
        if not isinstance(data, list):
            raise RuntimeError(f"Expected list from {url}, got {type(data)}")
        for item in data:
            yield item

        # parse Link header
        link = r.headers.get("Link", "")
        next_url = None
        for part in link.split(","):
            part = part.strip()
            if 'rel="next"' in part:
                m = re.search(r"<([^>]+)>", part)
                if m:
                    next_url = m.group(1)
        url = next_url


def ensure_label(s: requests.Session, owner: str, repo: str, name: str, color: str, description: str) -> bool:
    """Ensure label exists. Returns True if successful, False if no permissions."""
    # If exists -> 200; else 404
    r = s.get(f"{API}/repos/{owner}/{repo}/labels/{name}")
    if r.status_code == 200:
        return True
    if r.status_code == 403:
        return False  # No permission, skip this repo
    if r.status_code != 404:
        raise RuntimeError(f"GET label {owner}/{repo}:{name} failed: {r.status_code} {r.text}")

    r = s.post(
        f"{API}/repos/{owner}/{repo}/labels",
        json={"name": name, "color": color, "description": description},
    )
    if r.status_code not in (200, 201):
        # If 403, no permission to create labels
        if r.status_code == 403:
            return False
        # If it was created concurrently, GitHub may return 422. Treat as OK if label now exists.
        r2 = s.get(f"{API}/repos/{owner}/{repo}/labels/{name}")
        if r2.status_code == 200:
            return True
        raise RuntimeError(f"POST label {owner}/{repo}:{name} failed: {r.status_code} {r.text}")
    return True


def list_repos(s: requests.Session, owner: str) -> List[Dict[str, Any]]:
    """List all repos for an owner. Tries org endpoint first, falls back to user.

    Handles 403 (permission denied) and 404 (not found) gracefully by trying
    the next endpoint. This is important because GITHUB_TOKEN from Actions
    may not have org-level read access.
    """
    org_url = f"{API}/orgs/{owner}/repos"
    user_url = f"{API}/users/{owner}/repos"

    def try_url(url: str) -> Optional[List[Dict[str, Any]]]:
        try:
            return list(gh_get_paginated(s, url, params={"per_page": 100, "type": "all"}))
        except (GitHubNotFoundError, GitHubPermissionError) as e:
            print(f"  (info) {url} - {e}")
            return None
        except RuntimeError as e:
            # Unexpected error - log and move on to fallback
            print(f"  (warning) {url} - {e}")
            return None

    repos = try_url(org_url)
    if repos is None:
        repos = try_url(user_url)
    if repos is None:
        raise RuntimeError(f"Could not list repos for {owner} - both org and user endpoints failed")
    return repos


def classify(text: str) -> str:
    t = text.lower()
    if any(k in t for k in SECURITY_KEYWORDS):
        return "security"
    if any(k in t for k in BUG_KEYWORDS):
        return "bug"
    return "needs-triage"


def add_label(s: requests.Session, owner: str, repo: str, issue_number: int, label: str) -> bool:
    """Add label to issue. Returns True if successful, False if no permissions."""
    r = s.post(
        f"{API}/repos/{owner}/{repo}/issues/{issue_number}/labels",
        json={"labels": [label]},
    )
    if r.status_code not in (200, 201):
        if r.status_code == 403:
            return False  # No permission
        raise RuntimeError(f"Add label failed for {owner}/{repo}#{issue_number}: {r.status_code} {r.text}")
    return True


def main() -> int:
    # Prefer Actions' built-in token. Allow explicit override via TRIAGE_GH_TOKEN.
    triage_token = os.environ.get("TRIAGE_GH_TOKEN")
    github_token = os.environ.get("GITHUB_TOKEN")
    token = triage_token or github_token
    if not token:
        raise SystemExit("Missing env var: GITHUB_TOKEN (or TRIAGE_GH_TOKEN override)")
    token_source = "TRIAGE_GH_TOKEN" if triage_token else "GITHUB_TOKEN"
    owner = os.environ.get("GH_OWNER", "elvatis")
    prefix = os.environ.get("REPO_PREFIX", "openclaw-")
    per_repo_limit = int(os.environ.get("PER_REPO_LIMIT", "30"))
    skip_archived = bool_env("SKIP_ARCHIVED", True)
    skip_forks = bool_env("SKIP_FORKS", True)

    print(f"token source: {token_source}")
    if token_source == "GITHUB_TOKEN":
        print("[WARN] Using GITHUB_TOKEN - cross-repo labeling may be skipped without TRIAGE_GH_TOKEN.")

    s = session(token)

    # sanity check auth - use /octocat for installation tokens (doesn't require user scope)
    # The /user endpoint requires user-level access which GitHub Actions GITHUB_TOKEN doesn't have
    auth_check = s.get(f"{API}/octocat")
    if auth_check.status_code >= 400:
        raise RuntimeError(f"Auth check failed: {auth_check.status_code} {auth_check.text}")

    repos = list_repos(s, owner)
    repos = [r for r in repos if r.get("name", "").startswith(prefix)]
    if skip_archived:
        repos = [r for r in repos if not r.get("archived", False)]
    if skip_forks:
        repos = [r for r in repos if not r.get("fork", False)]

    repos.sort(key=lambda r: r.get("name", ""))

    total = Counts()
    per_repo: Dict[str, Counts] = {}
    skipped_repos: List[str] = []

    for r in repos:
        repo = r["name"]
        per_repo[repo] = Counts()

        try:
            # Ensure labels exist (check permissions first)
            has_access = True
            for lname, meta in LABELS.items():
                if not ensure_label(s, owner, repo, lname, meta["color"], meta["description"]):
                    has_access = False
                    break

            if not has_access:
                print(f"  [SKIP] {owner}/{repo}: insufficient permissions (cannot manage labels)")
                skipped_repos.append(repo)
                continue

            # Fetch open issues (GitHub's /issues includes PRs; filter them)
            issues = s.get(
                f"{API}/repos/{owner}/{repo}/issues",
                params={"state": "open", "per_page": per_repo_limit, "sort": "created", "direction": "desc"},
            )
            if issues.status_code == 403:
                print(f"  [SKIP] {owner}/{repo}: insufficient permissions to list issues")
                skipped_repos.append(repo)
                continue
            if issues.status_code >= 400:
                print(f"  [WARN] {owner}/{repo}: list issues returned {issues.status_code}, skipping")
                skipped_repos.append(repo)
                continue
            items = issues.json()
            if not isinstance(items, list):
                print(f"  [WARN] {owner}/{repo}: unexpected response from issues endpoint, skipping")
                skipped_repos.append(repo)
                continue

            for item in items:
                if "pull_request" in item:
                    continue

                number = int(item["number"])
                title = item.get("title") or ""
                body = item.get("body") or ""
                existing = {lbl.get("name") for lbl in item.get("labels", []) if isinstance(lbl, dict)}

                # Skip if already triaged as bug/security
                if "bug" in existing or "security" in existing:
                    continue

                label = classify(f"{title}\n{body}")
                if add_label(s, owner, repo, number, label):
                    c = per_repo[repo]
                    if label == "security":
                        c.security += 1
                        total.security += 1
                    elif label == "bug":
                        c.bug += 1
                        total.bug += 1
                    else:
                        c.needs_triage += 1
                        total.needs_triage += 1

        except Exception as exc:
            # Never let a single repo crash the entire triage run
            print(f"  [ERROR] {owner}/{repo}: {exc}")
            skipped_repos.append(repo)

    # Print a GitHub Actions-friendly summary
    print("\n== openclaw triage summary ==")
    print(f"repos scanned: {len(repos)}")
    if skipped_repos:
        print(f"repos skipped (no access): {len(skipped_repos)} - {', '.join(skipped_repos)}")
        print("  Hint: set TRIAGE_GH_TOKEN secret with a fine-grained PAT for cross-repo access.")
    print(f"labeled total: security={total.security}, bug={total.bug}, needs-triage={total.needs_triage}")
    print("\nPer repo:")
    for repo in sorted(per_repo.keys()):
        c = per_repo[repo]
        if c.security or c.bug or c.needs_triage:
            print(f"- {owner}/{repo}: security={c.security}, bug={c.bug}, needs-triage={c.needs_triage}")

    # Also write to the job summary if available
    summary_path = os.environ.get("GITHUB_STEP_SUMMARY")
    if summary_path:
        with open(summary_path, "a", encoding="utf-8") as f:
            f.write("## OpenClaw triage (labeling-only)\n\n")
            f.write(f"Repos scanned: **{len(repos)}**\n\n")
            if skipped_repos:
                f.write(f"Repos skipped (no access): **{len(skipped_repos)}** - {', '.join(skipped_repos)}\n\n")
            f.write(
                f"Labeled total: **security={total.security}**, **bug={total.bug}**, **needs-triage={total.needs_triage}**\n\n"
            )
            f.write("### Per repo (non-zero)\n\n")
            for repo in sorted(per_repo.keys()):
                c = per_repo[repo]
                if c.security or c.bug or c.needs_triage:
                    f.write(f"- `{owner}/{repo}`: security={c.security}, bug={c.bug}, needs-triage={c.needs_triage}\n")

    return 0


if __name__ == "__main__":
    try:
        raise SystemExit(main())
    except Exception as e:
        print(f"ERROR: {e}", file=sys.stderr)
        raise

```

openclaw-ops-elvatis | SkillHub