diff --git a/skills/k8s-quick-status/SKILL.md b/skills/k8s-quick-status/SKILL.md index 3af1329..3ffb5a5 100644 --- a/skills/k8s-quick-status/SKILL.md +++ b/skills/k8s-quick-status/SKILL.md @@ -1,43 +1,61 @@ --- name: k8s-quick-status -description: Quick cluster health pulse check -model: haiku +description: Quick cluster health pulse check. Use when asked about cluster status, k8s health, or kubernetes overview. +allowed-tools: + - Bash + - Read --- # K8s Quick Status Skill Performs a lightweight health pulse check on the Raspberry Pi Kubernetes cluster. +## Quick Command + +```bash +~/.claude/skills/k8s-quick-status/scripts/quick-status.sh +``` + +Or run individual checks: + +```bash +# Node status +kubectl get nodes -o wide + +# Unhealthy pods +kubectl get pods -A --field-selector=status.phase!=Running,status.phase!=Succeeded + +# Warning events +kubectl get events -A --field-selector=type=Warning --sort-by='.lastTimestamp' | tail -10 + +# ArgoCD apps +argocd app list +``` + ## Checks Performed -### Node Status -- Node readiness (`kubectl get nodes`) -- Node conditions (MemoryPressure, DiskPressure, PIDPressure) - -### Pod Health -- Unhealthy pods (`kubectl get pods -A --field-selector=status.phase!=Running`) -- High restart counts (pods with >5 restarts) - -### Recent Events -- Warning events in last hour (`kubectl get events -A --field-selector=type=Warning --sort-by='.lastTimestamp'`) - -### ArgoCD Status -- Application sync status (`argocd app list`) -- Any OutOfSync or Degraded apps +| Check | Command | What It Shows | +|-------|---------|---------------| +| Nodes | `kubectl get nodes` | Readiness, conditions | +| Pods | `kubectl get pods -A` | Unhealthy pods | +| Restarts | jsonpath query | Pods with >5 restarts | +| Events | `kubectl get events` | Recent warnings | +| ArgoCD | `argocd app list` | Sync status | ## Output Format -Report as structured summary with: -- Overall status (healthy/warning/critical) -- Node summary table -- Unhealthy pods list (if any) -- ArgoCD app status -- Immediate concerns (if any) - -## Autonomy - -This skill is read-only and can run without confirmation. +Report as structured summary: +- **Overall status**: healthy/warning/critical +- **Node summary**: table of nodes +- **Issues**: unhealthy pods, high restarts +- **ArgoCD**: sync status +- **Concerns**: immediate action items ## When to Use -Use this skill for a quick pulse check. For comprehensive analysis with metrics and detailed recommendations, use the `/cluster-status` command which invokes the full `cluster-health-check` workflow. +- Quick pulse check: use this skill +- Comprehensive analysis: use `/cluster-status` command + +## Autonomy + +Read-only - runs without confirmation. diff --git a/skills/k8s-quick-status/scripts/quick-status.sh b/skills/k8s-quick-status/scripts/quick-status.sh new file mode 100755 index 0000000..3ed5b26 --- /dev/null +++ b/skills/k8s-quick-status/scripts/quick-status.sh @@ -0,0 +1,28 @@ +#!/bin/bash +# Quick Kubernetes cluster status check +# Returns structured output for easy parsing + +set -euo pipefail + +echo "=== Node Status ===" +kubectl get nodes -o wide 2>/dev/null || echo "Error: Cannot reach cluster" + +echo "" +echo "=== Unhealthy Pods ===" +kubectl get pods -A --field-selector=status.phase!=Running,status.phase!=Succeeded 2>/dev/null | grep -v "^NAMESPACE" || echo "All pods healthy" + +echo "" +echo "=== High Restart Pods ===" +kubectl get pods -A -o jsonpath='{range .items[?(@.status.containerStatuses[0].restartCount>5)]}{.metadata.namespace}/{.metadata.name}: {.status.containerStatuses[0].restartCount} restarts{"\n"}{end}' 2>/dev/null || echo "No high restart pods" + +echo "" +echo "=== Recent Warning Events ===" +kubectl get events -A --field-selector=type=Warning --sort-by='.lastTimestamp' 2>/dev/null | tail -10 || echo "No warning events" + +echo "" +echo "=== ArgoCD Apps ===" +if command -v argocd &>/dev/null; then + argocd app list --output wide 2>/dev/null || echo "ArgoCD not accessible" +else + echo "ArgoCD CLI not installed" +fi diff --git a/skills/programmer-add-project/SKILL.md b/skills/programmer-add-project/SKILL.md index 07f8091..aed367e 100644 --- a/skills/programmer-add-project/SKILL.md +++ b/skills/programmer-add-project/SKILL.md @@ -1,6 +1,10 @@ --- name: programmer-add-project -description: Register a new project with the programmer agent system +description: Register a new project with the programmer agent system. Use when adding a new codebase or project. +allowed-tools: + - Bash + - Read + - Write --- # Add Project to Programmer Agent diff --git a/skills/sysadmin-health/SKILL.md b/skills/sysadmin-health/SKILL.md index fd507ae..f4141b0 100644 --- a/skills/sysadmin-health/SKILL.md +++ b/skills/sysadmin-health/SKILL.md @@ -1,51 +1,70 @@ --- name: sysadmin-health -description: Run comprehensive health check on Arch Linux workstation -model: haiku +description: Comprehensive health check on Arch Linux workstation. Use when asked about system health, disk space, memory, updates, or services. +allowed-tools: + - Bash + - Read --- # Sysadmin Health Check Skill Performs a comprehensive health check on the local Arch Linux workstation. +## Quick Command + +```bash +~/.claude/skills/sysadmin-health/scripts/health-check.sh +``` + +Or run individual checks: + +```bash +# Resources +df -h / /home +free -h +uptime + +# Packages +checkupdates | wc -l +yay -Qua | wc -l +pacman -Qtdq | wc -l + +# Services +systemctl --failed +systemctl --user --failed + +# Logs +journalctl -p err --since "24 hours ago" -n 10 +``` + ## Checks Performed -### System Resources -- Disk usage (`df -h`) -- Memory usage (`free -h`) -- Swap usage -- CPU load (`uptime`) -- Top processes by resource usage - -### Package Status -- Pending pacman updates (`checkupdates`) -- AUR updates available (`yay -Qua`) -- Orphaned packages (`pacman -Qtdq`) -- Package cache size (`du -sh /var/cache/pacman/pkg`) - -### Service Status -- Failed systemd services (`systemctl --failed`) -- Key services status (NetworkManager, sshd, etc.) - -### User Timers (Claude Automation) -- Active user timers (`systemctl --user list-timers`) -- Failed user services (`systemctl --user --failed`) -- Recent timer runs (`journalctl --user -u k8s-agent-health-check --since "24 hours ago" -n 5`) - -### Security -- Recent failed login attempts (`journalctl -u sshd --since "24 hours ago" | grep -i failed`) -- Last logins (`last -n 5`) - -### Logs -- Recent errors in journal (`journalctl -p err --since "24 hours ago" -n 20`) +| Category | Checks | +|----------|--------| +| **Resources** | Disk, memory, swap, CPU load | +| **Packages** | Pacman updates, AUR updates, orphans | +| **Services** | Failed systemd services (system + user) | +| **Security** | Recent logins, failed SSH attempts | +| **Logs** | Recent errors in journal | ## Output Format -Report as structured summary with: -- Overall health status (healthy/warning/critical) -- Issues found (if any) -- Recommended actions +Report as structured summary: +- **Overall health**: healthy/warning/critical +- **Resource usage**: disk %, memory % +- **Pending updates**: count +- **Failed services**: list +- **Issues found**: with recommendations + +## Thresholds + +| Metric | Warning | Critical | +|--------|---------|----------| +| Disk usage | >80% | >95% | +| Memory usage | >80% | >95% | +| Pending updates | >20 | >50 | +| Failed services | any | any critical | ## Autonomy -This skill is read-only and can run without confirmation. +Read-only - runs without confirmation. diff --git a/skills/sysadmin-health/scripts/health-check.sh b/skills/sysadmin-health/scripts/health-check.sh new file mode 100755 index 0000000..2062f11 --- /dev/null +++ b/skills/sysadmin-health/scripts/health-check.sh @@ -0,0 +1,45 @@ +#!/bin/bash +# Comprehensive Arch Linux health check +# Returns structured output for easy parsing + +set -euo pipefail + +echo "=== System Resources ===" +echo "--- Disk Usage ---" +df -h / /home 2>/dev/null || df -h + +echo "" +echo "--- Memory ---" +free -h + +echo "" +echo "--- Load ---" +uptime + +echo "" +echo "=== Package Status ===" +echo "--- Pending Updates ---" +checkupdates 2>/dev/null | wc -l | xargs echo "Pacman updates:" +yay -Qua 2>/dev/null | wc -l | xargs echo "AUR updates:" || echo "AUR updates: N/A" + +echo "" +echo "--- Orphaned Packages ---" +pacman -Qtdq 2>/dev/null | wc -l | xargs echo "Orphans:" || echo "Orphans: 0" + +echo "" +echo "=== Service Status ===" +echo "--- Failed Services ---" +systemctl --failed --no-pager 2>/dev/null || echo "No failed services" + +echo "" +echo "--- Failed User Services ---" +systemctl --user --failed --no-pager 2>/dev/null || echo "No failed user services" + +echo "" +echo "=== Recent Errors ===" +journalctl -p err --since "24 hours ago" -n 10 --no-pager 2>/dev/null || echo "No recent errors" + +echo "" +echo "=== Security ===" +echo "--- Recent Logins ---" +last -n 5 2>/dev/null || echo "No login history" diff --git a/skills/usage/SKILL.md b/skills/usage/SKILL.md index be4a42b..63bbaaf 100644 --- a/skills/usage/SKILL.md +++ b/skills/usage/SKILL.md @@ -1,6 +1,6 @@ --- name: usage -description: Track and report model usage across sessions +description: Track and report model usage across sessions. Use when asked about usage, stats, or session history. allowed-tools: - Bash - Read @@ -10,13 +10,13 @@ allowed-tools: Query session history and report usage statistics. -## Data Sources +## Quick Command -| Source | Status | Data Available | -|--------|--------|----------------| -| `history/index.json` | Available | Session IDs, start times | -| `history/*.jsonl` | Future | Session content, commands, tokens | -| `usage/config.json` | Available | Log level, preferences | +```bash +python3 ~/.claude/skills/usage/scripts/usage_report.py [range] +``` + +Ranges: `today`, `week` (default), `month`, `all` ## Command Routing @@ -27,11 +27,29 @@ Query session history and report usage statistics. | `/usage week` | Last 7 days | | `/usage month` | Last 30 days | | `/usage all` | All time stats | -| `/usage --by agent` | Group by agent | -| `/usage --by skill` | Group by skill | -| `/usage --by model` | Group by model tier | -| `/usage --set-level ` | Set log level | -| `/usage --show-config` | Show current config | + +## Data Sources + +| Source | Status | Data | +|--------|--------|------| +| `history/index.json` | Available | Session IDs, start times | +| `usage/config.json` | Available | Log level, preferences | + +## Output Format + +``` +šŸ“Š Usage Summary — Week + +Sessions: 12 +Total time: 8h 32m +Days active: 5 +Model: opus (primary) + +šŸ“… Sessions by Date: + 2026-01-01: 3 sessions (95m) + 2025-12-31: 4 sessions (120m) + ... +``` ## Log Levels @@ -41,111 +59,6 @@ Query session history and report usage statistics. | `standard` | + agents, skills, tokens (default) | | `detailed` | + commands, delegations, errors | -## Implementation - -### Query Sessions - -```python -import json -from datetime import datetime, timedelta -from pathlib import Path -from zoneinfo import ZoneInfo - -LOCAL_TZ = ZoneInfo('America/Los_Angeles') -HISTORY_DIR = Path.home() / ".claude/state/personal-assistant/history" -CONFIG_PATH = Path.home() / ".claude/state/usage/config.json" - -def load_sessions(): - index_path = HISTORY_DIR / "index.json" - if not index_path.exists(): - return [] - with open(index_path) as f: - data = json.load(f) - return data.get("sessions", []) - -def filter_sessions(sessions, range_type="week"): - now = datetime.now(LOCAL_TZ) - if range_type == "today": - cutoff = now.replace(hour=0, minute=0, second=0, microsecond=0) - elif range_type == "week": - cutoff = now - timedelta(days=7) - elif range_type == "month": - cutoff = now - timedelta(days=30) - else: # all - return sessions - - filtered = [] - for s in sessions: - started = datetime.fromisoformat(s["started"]) - if started >= cutoff: - filtered.append(s) - return filtered - -def estimate_duration(sessions): - """Estimate session duration from consecutive start times.""" - if not sessions: - return [] - - sorted_sessions = sorted(sessions, key=lambda s: s["started"]) - for i, session in enumerate(sorted_sessions): - if i + 1 < len(sorted_sessions): - start = datetime.fromisoformat(session["started"]) - next_start = datetime.fromisoformat(sorted_sessions[i + 1]["started"]) - duration = (next_start - start).total_seconds() / 60 - # Cap at 2 hours (likely session gap) - session["duration_mins"] = min(duration, 120) - else: - # Current/last session: estimate 30 mins - session["duration_mins"] = 30 - return sorted_sessions -``` - -### Generate Report - -```python -def generate_report(sessions, range_type="week"): - total_mins = sum(s.get("duration_mins", 0) for s in sessions) - hours = int(total_mins // 60) - mins = int(total_mins % 60) - - report = f""" -šŸ“Š Usage Summary — {range_type.title()} - -Sessions: {len(sessions)} -Total time: {hours}h {mins}m -Model: opus (primary) -""" - return report -``` - -## Output Format - -``` -šŸ“Š Usage Summary — Last 7 Days - -Sessions: 12 -Total time: 8h 32m -Model: opus (primary) - -ā”Œā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”¬ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”¬ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā” -│ Agent │ Sessions │ Time │ -ā”œā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”¼ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”¼ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”¤ -│ PA │ 12 │ 8h 32m │ -ā””ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”“ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”“ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”˜ - -Note: Detailed metrics available when session -content logging is enabled. -``` - -## Future Enhancements - -When `history/*.jsonl` files are available: -- Parse actual commands used -- Detect skill invocations -- Count delegations (sonnet/haiku) -- Estimate token usage -- Track errors - ## Policy - Read-only operations diff --git a/skills/usage/scripts/usage_report.py b/skills/usage/scripts/usage_report.py new file mode 100755 index 0000000..9ac9f7a --- /dev/null +++ b/skills/usage/scripts/usage_report.py @@ -0,0 +1,109 @@ +#!/usr/bin/env python3 +"""Generate usage statistics from session history.""" +import json +import sys +from datetime import datetime, timedelta +from pathlib import Path +from zoneinfo import ZoneInfo + +LOCAL_TZ = ZoneInfo('America/Los_Angeles') +HISTORY_DIR = Path.home() / ".claude/state/personal-assistant/history" +CONFIG_PATH = Path.home() / ".claude/state/usage/config.json" + + +def load_sessions(): + """Load sessions from history index.""" + index_path = HISTORY_DIR / "index.json" + if not index_path.exists(): + return [] + with open(index_path) as f: + data = json.load(f) + return data.get("sessions", []) + + +def filter_sessions(sessions, range_type="week"): + """Filter sessions by time range.""" + now = datetime.now(LOCAL_TZ) + if range_type == "today": + cutoff = now.replace(hour=0, minute=0, second=0, microsecond=0) + elif range_type == "week": + cutoff = now - timedelta(days=7) + elif range_type == "month": + cutoff = now - timedelta(days=30) + else: # all + return sessions + + filtered = [] + for s in sessions: + try: + started = datetime.fromisoformat(s["started"]) + if started.tzinfo is None: + started = started.replace(tzinfo=LOCAL_TZ) + if started >= cutoff: + filtered.append(s) + except (KeyError, ValueError): + continue + return filtered + + +def estimate_duration(sessions): + """Estimate session duration from consecutive start times.""" + if not sessions: + return [] + + sorted_sessions = sorted(sessions, key=lambda s: s["started"]) + for i, session in enumerate(sorted_sessions): + if i + 1 < len(sorted_sessions): + start = datetime.fromisoformat(session["started"]) + next_start = datetime.fromisoformat(sorted_sessions[i + 1]["started"]) + duration = (next_start - start).total_seconds() / 60 + # Cap at 2 hours (likely session gap) + session["duration_mins"] = min(duration, 120) + else: + # Current/last session: estimate 30 mins + session["duration_mins"] = 30 + return sorted_sessions + + +def generate_report(sessions, range_type="week"): + """Generate usage report.""" + sessions = estimate_duration(sessions) + total_mins = sum(s.get("duration_mins", 0) for s in sessions) + hours = int(total_mins // 60) + mins = int(total_mins % 60) + + # Group by date + by_date = {} + for s in sessions: + date = s["started"][:10] + by_date.setdefault(date, []).append(s) + + report = f"""šŸ“Š Usage Summary — {range_type.title()} + +Sessions: {len(sessions)} +Total time: {hours}h {mins}m +Days active: {len(by_date)} +Model: opus (primary) +""" + + if by_date: + report += "\nšŸ“… Sessions by Date:\n" + for date in sorted(by_date.keys(), reverse=True)[:7]: + count = len(by_date[date]) + day_mins = sum(s.get("duration_mins", 0) for s in by_date[date]) + report += f" {date}: {count} sessions ({int(day_mins)}m)\n" + + return report + + +def main(): + range_type = sys.argv[1] if len(sys.argv) > 1 else "week" + + sessions = load_sessions() + filtered = filter_sessions(sessions, range_type) + report = generate_report(filtered, range_type) + print(report) + + +if __name__ == "__main__": + main()