sysops · diff

git:20260512.5cb72fd to git:20260512.69b6425

10 added, 152 removed. Audit A to A.

---
name: sysops
description: Invoke the sysops subagent for VPS maintenance. Use when the user runs /sysops with an optional argument like "status", "update Pulse", or "update my servers".
---
# Sysops
If the argument starts with `log` or `learnings`, handle it directly — do NOT delegate to the sysops subagent.
## /sysops log
Parse the argument:
- `/sysops log` → show last 10 entries across all deployments
- `/sysops log <Deployment>` → show last 10 entries for that deployment (case-insensitive match)
- `/sysops log <Deployment> --last <N>` → show last N entries for that deployment
- Run this command, substituting `DEPLOYMENT_FILTER` (empty string `""` if no deployment was given) and `LIMIT` (default `10`):
+ Run this command, substituting `<deployment>` (omit `--deployment` entirely if no deployment was given) and `<N>` (default `10`):
```bash
- DEPLOYMENT_FILTER="<filter or empty string>" LIMIT=<N> python3 -c "
- import json, os, sys
-
- log_file = os.path.expanduser('~/.claude/sysops/audit.jsonl')
- deployment_filter = os.environ.get('DEPLOYMENT_FILTER', '')
- limit = int(os.environ.get('LIMIT', '10'))
-
- if not os.path.exists(log_file):
- print('No audit log found at ~/.claude/sysops/audit.jsonl')
- print('Logs are written after each /sysops status or /sysops update run.')
- sys.exit(0)
-
- entries = []
- corrupt_count = 0
- try:
- with open(log_file) as f:
- for line in f:
- line = line.strip()
- if not line:
- continue
- try:
- obj = json.loads(line)
- except json.JSONDecodeError:
- corrupt_count += 1
- continue
- if deployment_filter and obj.get('deployment', '').lower() != deployment_filter.lower():
- continue
- entries.append(obj)
- except PermissionError:
- print(f'Error: cannot read audit log at {log_file} (permission denied).')
- sys.exit(1)
- except OSError as e:
- print(f'Error: cannot read audit log: {e}')
- sys.exit(1)
-
- entries = entries[-limit:]
-
- if not entries:
- msg = 'No entries found'
- if deployment_filter:
- msg += f' for deployment {deployment_filter!r}'
- print(msg + '.')
- if corrupt_count:
- print(f'Warning: {corrupt_count} corrupt line(s) skipped in audit log.')
- sys.exit(0)
-
- for e in entries:
- ts = e.get('ts', 'unknown')
- dep = e.get('deployment', 'unknown')
- env = e.get('env', '?')
- action = e.get('action', '?')
- project = e.get('project', '?')
- print(f'{ts} {dep} [{env}] {action} (project: {project})')
-
- if action == 'maintenance':
- pkgs = e.get('packages_upgraded', 0)
- reboot = e.get('reboot_performed', False)
- dur = e.get('reboot_duration_s')
- reboot_str = f'yes ({dur}s)' if reboot and dur else ('yes' if reboot else 'no')
- print(f' Packages upgraded: {pkgs} · Reboot: {reboot_str}')
-
- containers = e.get('containers', {})
- if containers:
- parts = [n + (' ✓' if s == 'healthy' else ' ✗') for n, s in containers.items()]
- print(' Containers: ' + ' '.join(parts))
-
- unregistered = e.get('unregistered_containers', [])
- unreg_str = ' '.join(u + ' ⚠' for u in unregistered) if unregistered else 'none'
- print(f' Unregistered: {unreg_str}')
-
- notes = e.get('notes', '')
- if notes:
- print(f' Notes: {notes}')
- print()
-
- if corrupt_count:
- print(f'Warning: {corrupt_count} corrupt line(s) skipped in audit log.')
- "
+ trimkit-sysops-log-search --human [--deployment "<deployment>"] [--last <N>]
```
+ If `trimkit-sysops-log-search` is not on PATH, tell the user:
+ > `trimkit-sysops-log-search` is not installed. Run `install.sh` from your trimkit directory to set it up.
+
---
## /sysops learnings
Parse the argument:
- `/sysops learnings` → show all stored learnings across all deployments (deduplicated)
- `/sysops learnings <Deployment>` → show learnings for that deployment only (case-insensitive match)
- Run this command, substituting `DEPLOYMENT_FILTER` (empty string `""` if no deployment was given):
+ Run this command, substituting `<deployment>` (omit `--deployment` entirely if no deployment was given):
```bash
- DEPLOYMENT_FILTER="<filter or empty string>" python3 -c "
- import json, os, sys
-
- learnings_file = os.path.expanduser('~/.claude/sysops/learnings.jsonl')
- deployment_filter = os.environ.get('DEPLOYMENT_FILTER', '')
-
- if not os.path.exists(learnings_file):
- print('No learnings stored yet at ~/.claude/sysops/learnings.jsonl')
- print('Learnings are written by the sysops agent when it discovers server quirks.')
- sys.exit(0)
-
- # Read all entries and deduplicate by (deployment, key) pair — latest entry per
- # pair wins. Scoping dedup to deployment prevents a key on one server from
- # suppressing the same key on another.
- seen = {} # (deployment, key) -> entry
- order = [] # first-seen (deployment, key) pairs in insertion order
- corrupt_count = 0
- try:
- with open(learnings_file) as f:
- for line in f:
- line = line.strip()
- if not line:
- continue
- try:
- obj = json.loads(line)
- except json.JSONDecodeError:
- corrupt_count += 1
- continue
- dedup_key = (obj.get('deployment', ''), obj.get('key', ''))
- if dedup_key not in seen:
- order.append(dedup_key)
- seen[dedup_key] = obj
- except PermissionError:
- print(f'Error: cannot read learnings at {learnings_file} (permission denied).', file=sys.stderr)
- sys.exit(1)
- except OSError as e:
- print(f'Error: cannot read learnings: {e}', file=sys.stderr)
- sys.exit(1)
-
- # Filter and collect surviving entries in first-seen order.
- entries = []
- for dedup_key in order:
- entry = seen[dedup_key]
- if deployment_filter and entry.get('deployment', '').lower() != deployment_filter.lower():
- continue
- entries.append(entry)
-
- if not entries:
- msg = 'No learnings found'
- if deployment_filter:
- msg += f' for deployment {deployment_filter!r}'
- print(msg + '.')
- if corrupt_count:
- print(f'Warning: {corrupt_count} corrupt line(s) skipped.')
- sys.exit(0)
-
- for e in entries:
- ts = e.get('ts', 'unknown')
- dep = e.get('deployment', 'unknown')
- key = e.get('key', '?')
- ltype = e.get('type', '?')
- insight = e.get('insight', '')
- confidence = e.get('confidence', '?')
- source = e.get('source', '?')
- print(f'{dep} [{ltype}] {key} (confidence: {confidence}, source: {source}, recorded: {ts})')
- if insight:
- print(f' {insight}')
- print()
-
- if corrupt_count:
- print(f'Warning: {corrupt_count} corrupt line(s) skipped in learnings store.')
- "
+ trimkit-learnings-search --human [--deployment "<deployment>"]
```
+
+ If `trimkit-learnings-search` is not on PATH, tell the user:
+ > `trimkit-learnings-search` is not installed. Run `install.sh` from your trimkit directory to set it up.
---
Otherwise, delegate to the `sysops` subagent using the Agent tool.
Pass the user's argument directly as the prompt. If no argument was given, default to a status check across all deployments.
Examples:
- `/sysops` → prompt: "Check the status of all my servers"
- `/sysops status` → prompt: "Check the status of all my servers"
- `/sysops update Pulse` → prompt: "Update Pulse"
- `/sysops update my servers` → prompt: "Update all my servers"
Do not add any preamble or explanation — just invoke the subagent immediately.